chore: Ai chat context (#6929)

* chore: implement chat setting

* chore: clippy

* chore: rename

* chore: set rag_ids when creating a chat

* chore: clippy

* chore: fix test

* chore: fix test

* chore: fix test

* chore: clippy
This commit is contained in:
Nathan.fooo
2024-12-08 18:25:25 +08:00
committed by GitHub
parent bb50466aa9
commit 722b436cad
105 changed files with 848 additions and 1119 deletions

View File

@ -18,7 +18,7 @@ on:
env:
CARGO_TERM_COLOR: always
CLOUD_VERSION: 0.7.6-amd64
CLOUD_VERSION: 0.8.3-amd64
RUST_TOOLCHAIN: "1.80.1"
jobs:

View File

@ -77,19 +77,19 @@ class ChatFile extends Equatable {
final fileName = path.basename(filePath);
final extension = path.extension(filePath).toLowerCase();
ChatMessageMetaTypePB fileType;
ContextLoaderTypePB fileType;
switch (extension) {
case '.pdf':
fileType = ChatMessageMetaTypePB.PDF;
fileType = ContextLoaderTypePB.PDF;
break;
case '.txt':
fileType = ChatMessageMetaTypePB.Txt;
fileType = ContextLoaderTypePB.Txt;
break;
case '.md':
fileType = ChatMessageMetaTypePB.Markdown;
fileType = ContextLoaderTypePB.Markdown;
break;
default:
fileType = ChatMessageMetaTypePB.UnknownMetaType;
fileType = ContextLoaderTypePB.UnknownLoaderType;
}
return ChatFile(
@ -101,7 +101,7 @@ class ChatFile extends Equatable {
final String filePath;
final String fileName;
final ChatMessageMetaTypePB fileType;
final ContextLoaderTypePB fileType;
@override
List<Object?> get props => [filePath];

View File

@ -138,7 +138,7 @@ Future<List<ChatMessageMetaPB>> metadataPBFromMetadata(
id: value.id,
name: value.name,
data: pb.text,
dataType: ChatMessageMetaTypePB.Txt,
loaderType: ContextLoaderTypePB.Txt,
source: appflowySource,
),
);
@ -156,7 +156,7 @@ Future<List<ChatMessageMetaPB>> metadataPBFromMetadata(
id: nanoid(8),
name: fileName,
data: filePath,
dataType: fileType,
loaderType: fileType,
source: filePath,
),
);

View File

@ -18,11 +18,11 @@ class ImportPayload {
class ImportBackendService {
static Future<FlowyResult<RepeatedViewPB, FlowyError>> importPages(
String parentViewId,
List<ImportValuePayloadPB> values,
List<ImportItemPayloadPB> values,
) async {
final request = ImportPayloadPB(
parentViewId: parentViewId,
values: values,
items: values,
);
return FolderEventImportData(request).send();

View File

@ -151,7 +151,7 @@ class _ImportPanelState extends State<ImportPanel> {
showLoading.value = true;
final importValues = <ImportValuePayloadPB>[];
final importValues = <ImportItemPayloadPB>[];
for (final file in result.files) {
final path = file.path;
if (path == null) {
@ -163,7 +163,7 @@ class _ImportPanelState extends State<ImportPanel> {
case ImportType.historyDatabase:
final data = await File(path).readAsString();
importValues.add(
ImportValuePayloadPB.create()
ImportItemPayloadPB.create()
..name = name
..data = utf8.encode(data)
..viewLayout = ViewLayoutPB.Grid
@ -176,7 +176,7 @@ class _ImportPanelState extends State<ImportPanel> {
final bytes = _documentDataFrom(importType, data);
if (bytes != null) {
importValues.add(
ImportValuePayloadPB.create()
ImportItemPayloadPB.create()
..name = name
..data = bytes
..viewLayout = ViewLayoutPB.Document
@ -187,7 +187,7 @@ class _ImportPanelState extends State<ImportPanel> {
case ImportType.csv:
final data = await File(path).readAsString();
importValues.add(
ImportValuePayloadPB.create()
ImportItemPayloadPB.create()
..name = name
..data = utf8.encode(data)
..viewLayout = ViewLayoutPB.Grid
@ -197,7 +197,7 @@ class _ImportPanelState extends State<ImportPanel> {
case ImportType.afDatabase:
final data = await File(path).readAsString();
importValues.add(
ImportValuePayloadPB.create()
ImportItemPayloadPB.create()
..name = name
..data = utf8.encode(data)
..viewLayout = ViewLayoutPB.Grid

View File

@ -121,7 +121,7 @@ class AppFlowyGridTest {
final context = await ImportBackendService.importPages(
workspace.id,
[
ImportValuePayloadPB()
ImportItemPayloadPB()
..name = fileName
..data = utf8.encode(data)
..viewLayout = ViewLayoutPB.Grid

View File

@ -1030,7 +1030,7 @@ dependencies = [
[[package]]
name = "collab"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=35181c77b448d2ac9cbfbd729790bd98c5d1a979#35181c77b448d2ac9cbfbd729790bd98c5d1a979"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=79bc18e4651c17c83d9f00a5ce79423398e88ea1#79bc18e4651c17c83d9f00a5ce79423398e88ea1"
dependencies = [
"anyhow",
"arc-swap",
@ -1055,7 +1055,7 @@ dependencies = [
[[package]]
name = "collab-database"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=35181c77b448d2ac9cbfbd729790bd98c5d1a979#35181c77b448d2ac9cbfbd729790bd98c5d1a979"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=79bc18e4651c17c83d9f00a5ce79423398e88ea1#79bc18e4651c17c83d9f00a5ce79423398e88ea1"
dependencies = [
"anyhow",
"async-trait",
@ -1094,7 +1094,7 @@ dependencies = [
[[package]]
name = "collab-document"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=35181c77b448d2ac9cbfbd729790bd98c5d1a979#35181c77b448d2ac9cbfbd729790bd98c5d1a979"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=79bc18e4651c17c83d9f00a5ce79423398e88ea1#79bc18e4651c17c83d9f00a5ce79423398e88ea1"
dependencies = [
"anyhow",
"arc-swap",
@ -1115,7 +1115,7 @@ dependencies = [
[[package]]
name = "collab-entity"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=35181c77b448d2ac9cbfbd729790bd98c5d1a979#35181c77b448d2ac9cbfbd729790bd98c5d1a979"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=79bc18e4651c17c83d9f00a5ce79423398e88ea1#79bc18e4651c17c83d9f00a5ce79423398e88ea1"
dependencies = [
"anyhow",
"bytes",
@ -1135,7 +1135,7 @@ dependencies = [
[[package]]
name = "collab-folder"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=35181c77b448d2ac9cbfbd729790bd98c5d1a979#35181c77b448d2ac9cbfbd729790bd98c5d1a979"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=79bc18e4651c17c83d9f00a5ce79423398e88ea1#79bc18e4651c17c83d9f00a5ce79423398e88ea1"
dependencies = [
"anyhow",
"arc-swap",
@ -1157,7 +1157,7 @@ dependencies = [
[[package]]
name = "collab-importer"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=35181c77b448d2ac9cbfbd729790bd98c5d1a979#35181c77b448d2ac9cbfbd729790bd98c5d1a979"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=79bc18e4651c17c83d9f00a5ce79423398e88ea1#79bc18e4651c17c83d9f00a5ce79423398e88ea1"
dependencies = [
"anyhow",
"async-recursion",
@ -1218,7 +1218,7 @@ dependencies = [
[[package]]
name = "collab-plugins"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=35181c77b448d2ac9cbfbd729790bd98c5d1a979#35181c77b448d2ac9cbfbd729790bd98c5d1a979"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=79bc18e4651c17c83d9f00a5ce79423398e88ea1#79bc18e4651c17c83d9f00a5ce79423398e88ea1"
dependencies = [
"anyhow",
"async-stream",
@ -1298,7 +1298,7 @@ dependencies = [
[[package]]
name = "collab-user"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=35181c77b448d2ac9cbfbd729790bd98c5d1a979#35181c77b448d2ac9cbfbd729790bd98c5d1a979"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=79bc18e4651c17c83d9f00a5ce79423398e88ea1#79bc18e4651c17c83d9f00a5ce79423398e88ea1"
dependencies = [
"anyhow",
"collab",
@ -2484,6 +2484,7 @@ dependencies = [
"collab-folder",
"collab-integrate",
"collab-plugins",
"dashmap 6.0.1",
"flowy-codegen",
"flowy-derive",
"flowy-error",

View File

@ -120,14 +120,14 @@ custom-protocol = ["tauri/custom-protocol"]
# To switch to the local path, run:
# scripts/tool/update_collab_source.sh
# ⚠️⚠️⚠️️
collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "35181c77b448d2ac9cbfbd729790bd98c5d1a979" }
collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "35181c77b448d2ac9cbfbd729790bd98c5d1a979" }
collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "35181c77b448d2ac9cbfbd729790bd98c5d1a979" }
collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "35181c77b448d2ac9cbfbd729790bd98c5d1a979" }
collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "35181c77b448d2ac9cbfbd729790bd98c5d1a979" }
collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "35181c77b448d2ac9cbfbd729790bd98c5d1a979" }
collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "35181c77b448d2ac9cbfbd729790bd98c5d1a979" }
collab-importer = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "35181c77b448d2ac9cbfbd729790bd98c5d1a979" }
collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "79bc18e4651c17c83d9f00a5ce79423398e88ea1" }
collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "79bc18e4651c17c83d9f00a5ce79423398e88ea1" }
collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "79bc18e4651c17c83d9f00a5ce79423398e88ea1" }
collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "79bc18e4651c17c83d9f00a5ce79423398e88ea1" }
collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "79bc18e4651c17c83d9f00a5ce79423398e88ea1" }
collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "79bc18e4651c17c83d9f00a5ce79423398e88ea1" }
collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "79bc18e4651c17c83d9f00a5ce79423398e88ea1" }
collab-importer = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "79bc18e4651c17c83d9f00a5ce79423398e88ea1" }
# Working directory: frontend
# To update the commit ID, run:

View File

@ -1028,7 +1028,7 @@ dependencies = [
[[package]]
name = "collab"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=35181c77b448d2ac9cbfbd729790bd98c5d1a979#35181c77b448d2ac9cbfbd729790bd98c5d1a979"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=79bc18e4651c17c83d9f00a5ce79423398e88ea1#79bc18e4651c17c83d9f00a5ce79423398e88ea1"
dependencies = [
"anyhow",
"arc-swap",
@ -1053,7 +1053,7 @@ dependencies = [
[[package]]
name = "collab-database"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=35181c77b448d2ac9cbfbd729790bd98c5d1a979#35181c77b448d2ac9cbfbd729790bd98c5d1a979"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=79bc18e4651c17c83d9f00a5ce79423398e88ea1#79bc18e4651c17c83d9f00a5ce79423398e88ea1"
dependencies = [
"anyhow",
"async-trait",
@ -1092,7 +1092,7 @@ dependencies = [
[[package]]
name = "collab-document"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=35181c77b448d2ac9cbfbd729790bd98c5d1a979#35181c77b448d2ac9cbfbd729790bd98c5d1a979"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=79bc18e4651c17c83d9f00a5ce79423398e88ea1#79bc18e4651c17c83d9f00a5ce79423398e88ea1"
dependencies = [
"anyhow",
"arc-swap",
@ -1113,7 +1113,7 @@ dependencies = [
[[package]]
name = "collab-entity"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=35181c77b448d2ac9cbfbd729790bd98c5d1a979#35181c77b448d2ac9cbfbd729790bd98c5d1a979"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=79bc18e4651c17c83d9f00a5ce79423398e88ea1#79bc18e4651c17c83d9f00a5ce79423398e88ea1"
dependencies = [
"anyhow",
"bytes",
@ -1133,7 +1133,7 @@ dependencies = [
[[package]]
name = "collab-folder"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=35181c77b448d2ac9cbfbd729790bd98c5d1a979#35181c77b448d2ac9cbfbd729790bd98c5d1a979"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=79bc18e4651c17c83d9f00a5ce79423398e88ea1#79bc18e4651c17c83d9f00a5ce79423398e88ea1"
dependencies = [
"anyhow",
"arc-swap",
@ -1155,7 +1155,7 @@ dependencies = [
[[package]]
name = "collab-importer"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=35181c77b448d2ac9cbfbd729790bd98c5d1a979#35181c77b448d2ac9cbfbd729790bd98c5d1a979"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=79bc18e4651c17c83d9f00a5ce79423398e88ea1#79bc18e4651c17c83d9f00a5ce79423398e88ea1"
dependencies = [
"anyhow",
"async-recursion",
@ -1216,7 +1216,7 @@ dependencies = [
[[package]]
name = "collab-plugins"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=35181c77b448d2ac9cbfbd729790bd98c5d1a979#35181c77b448d2ac9cbfbd729790bd98c5d1a979"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=79bc18e4651c17c83d9f00a5ce79423398e88ea1#79bc18e4651c17c83d9f00a5ce79423398e88ea1"
dependencies = [
"anyhow",
"async-stream",
@ -1296,7 +1296,7 @@ dependencies = [
[[package]]
name = "collab-user"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=35181c77b448d2ac9cbfbd729790bd98c5d1a979#35181c77b448d2ac9cbfbd729790bd98c5d1a979"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=79bc18e4651c17c83d9f00a5ce79423398e88ea1#79bc18e4651c17c83d9f00a5ce79423398e88ea1"
dependencies = [
"anyhow",
"collab",
@ -2529,6 +2529,7 @@ dependencies = [
"collab-folder",
"collab-integrate",
"collab-plugins",
"dashmap 6.0.1",
"flowy-codegen",
"flowy-derive",
"flowy-error",

View File

@ -118,14 +118,14 @@ custom-protocol = ["tauri/custom-protocol"]
# To switch to the local path, run:
# scripts/tool/update_collab_source.sh
# ⚠️⚠️⚠️️
collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "35181c77b448d2ac9cbfbd729790bd98c5d1a979" }
collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "35181c77b448d2ac9cbfbd729790bd98c5d1a979" }
collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "35181c77b448d2ac9cbfbd729790bd98c5d1a979" }
collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "35181c77b448d2ac9cbfbd729790bd98c5d1a979" }
collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "35181c77b448d2ac9cbfbd729790bd98c5d1a979" }
collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "35181c77b448d2ac9cbfbd729790bd98c5d1a979" }
collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "35181c77b448d2ac9cbfbd729790bd98c5d1a979" }
collab-importer = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "35181c77b448d2ac9cbfbd729790bd98c5d1a979" }
collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "79bc18e4651c17c83d9f00a5ce79423398e88ea1" }
collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "79bc18e4651c17c83d9f00a5ce79423398e88ea1" }
collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "79bc18e4651c17c83d9f00a5ce79423398e88ea1" }
collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "79bc18e4651c17c83d9f00a5ce79423398e88ea1" }
collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "79bc18e4651c17c83d9f00a5ce79423398e88ea1" }
collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "79bc18e4651c17c83d9f00a5ce79423398e88ea1" }
collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "79bc18e4651c17c83d9f00a5ce79423398e88ea1" }
collab-importer = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "79bc18e4651c17c83d9f00a5ce79423398e88ea1" }
# Working directory: frontend

View File

@ -891,7 +891,7 @@ dependencies = [
[[package]]
name = "collab"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=35181c77b448d2ac9cbfbd729790bd98c5d1a979#35181c77b448d2ac9cbfbd729790bd98c5d1a979"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=79bc18e4651c17c83d9f00a5ce79423398e88ea1#79bc18e4651c17c83d9f00a5ce79423398e88ea1"
dependencies = [
"anyhow",
"arc-swap",
@ -916,7 +916,7 @@ dependencies = [
[[package]]
name = "collab-database"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=35181c77b448d2ac9cbfbd729790bd98c5d1a979#35181c77b448d2ac9cbfbd729790bd98c5d1a979"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=79bc18e4651c17c83d9f00a5ce79423398e88ea1#79bc18e4651c17c83d9f00a5ce79423398e88ea1"
dependencies = [
"anyhow",
"async-trait",
@ -955,7 +955,7 @@ dependencies = [
[[package]]
name = "collab-document"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=35181c77b448d2ac9cbfbd729790bd98c5d1a979#35181c77b448d2ac9cbfbd729790bd98c5d1a979"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=79bc18e4651c17c83d9f00a5ce79423398e88ea1#79bc18e4651c17c83d9f00a5ce79423398e88ea1"
dependencies = [
"anyhow",
"arc-swap",
@ -976,7 +976,7 @@ dependencies = [
[[package]]
name = "collab-entity"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=35181c77b448d2ac9cbfbd729790bd98c5d1a979#35181c77b448d2ac9cbfbd729790bd98c5d1a979"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=79bc18e4651c17c83d9f00a5ce79423398e88ea1#79bc18e4651c17c83d9f00a5ce79423398e88ea1"
dependencies = [
"anyhow",
"bytes",
@ -996,7 +996,7 @@ dependencies = [
[[package]]
name = "collab-folder"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=35181c77b448d2ac9cbfbd729790bd98c5d1a979#35181c77b448d2ac9cbfbd729790bd98c5d1a979"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=79bc18e4651c17c83d9f00a5ce79423398e88ea1#79bc18e4651c17c83d9f00a5ce79423398e88ea1"
dependencies = [
"anyhow",
"arc-swap",
@ -1018,7 +1018,7 @@ dependencies = [
[[package]]
name = "collab-importer"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=35181c77b448d2ac9cbfbd729790bd98c5d1a979#35181c77b448d2ac9cbfbd729790bd98c5d1a979"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=79bc18e4651c17c83d9f00a5ce79423398e88ea1#79bc18e4651c17c83d9f00a5ce79423398e88ea1"
dependencies = [
"anyhow",
"async-recursion",
@ -1079,7 +1079,7 @@ dependencies = [
[[package]]
name = "collab-plugins"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=35181c77b448d2ac9cbfbd729790bd98c5d1a979#35181c77b448d2ac9cbfbd729790bd98c5d1a979"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=79bc18e4651c17c83d9f00a5ce79423398e88ea1#79bc18e4651c17c83d9f00a5ce79423398e88ea1"
dependencies = [
"anyhow",
"async-stream",
@ -1159,7 +1159,7 @@ dependencies = [
[[package]]
name = "collab-user"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=35181c77b448d2ac9cbfbd729790bd98c5d1a979#35181c77b448d2ac9cbfbd729790bd98c5d1a979"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=79bc18e4651c17c83d9f00a5ce79423398e88ea1#79bc18e4651c17c83d9f00a5ce79423398e88ea1"
dependencies = [
"anyhow",
"collab",
@ -2349,6 +2349,7 @@ dependencies = [
"collab-folder",
"collab-integrate",
"collab-plugins",
"dashmap 6.0.1",
"flowy-codegen",
"flowy-derive",
"flowy-error",

View File

@ -142,14 +142,14 @@ rocksdb = { git = "https://github.com/rust-rocksdb/rust-rocksdb", rev = "1710120
# To switch to the local path, run:
# scripts/tool/update_collab_source.sh
# ⚠️⚠️⚠️️
collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "35181c77b448d2ac9cbfbd729790bd98c5d1a979" }
collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "35181c77b448d2ac9cbfbd729790bd98c5d1a979" }
collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "35181c77b448d2ac9cbfbd729790bd98c5d1a979" }
collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "35181c77b448d2ac9cbfbd729790bd98c5d1a979" }
collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "35181c77b448d2ac9cbfbd729790bd98c5d1a979" }
collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "35181c77b448d2ac9cbfbd729790bd98c5d1a979" }
collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "35181c77b448d2ac9cbfbd729790bd98c5d1a979" }
collab-importer = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "35181c77b448d2ac9cbfbd729790bd98c5d1a979" }
collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "79bc18e4651c17c83d9f00a5ce79423398e88ea1" }
collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "79bc18e4651c17c83d9f00a5ce79423398e88ea1" }
collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "79bc18e4651c17c83d9f00a5ce79423398e88ea1" }
collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "79bc18e4651c17c83d9f00a5ce79423398e88ea1" }
collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "79bc18e4651c17c83d9f00a5ce79423398e88ea1" }
collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "79bc18e4651c17c83d9f00a5ce79423398e88ea1" }
collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "79bc18e4651c17c83d9f00a5ce79423398e88ea1" }
collab-importer = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "79bc18e4651c17c83d9f00a5ce79423398e88ea1" }
# Working directory: frontend
# To update the commit ID, run:

View File

@ -1,4 +1,4 @@
use flowy_folder::view_operation::{EncodedCollabWrapper, ViewData};
use flowy_folder::view_operation::{EncodedCollabType, ViewData};
use std::sync::Arc;
use collab_folder::{FolderData, View};
@ -165,15 +165,12 @@ impl EventIntegrationTest {
}
pub async fn get_folder_data(&self) -> FolderData {
let mutex_folder = self
self
.appflowy_core
.folder_manager
.get_mutex_folder()
.clone()
.unwrap();
let folder = mutex_folder.read().await;
let workspace_id = self.appflowy_core.user_manager.workspace_id().unwrap();
folder.get_folder_data(&workspace_id).clone().unwrap()
.get_folder_data()
.await
.unwrap()
}
pub async fn get_publish_payload(
@ -197,13 +194,10 @@ impl EventIntegrationTest {
&self,
view_id: &str,
layout: ViewLayout,
) -> EncodedCollabWrapper {
let manager = self.folder_manager.clone();
let user = manager.get_user().clone();
let handlers = manager.get_operation_handlers();
let handler = handlers.get(&layout).unwrap();
handler
.get_encoded_collab_v1_from_disk(user, view_id)
) -> EncodedCollabType {
self
.folder_manager
.get_encode_collab_from_disk(view_id, &layout)
.await
.unwrap()
}

View File

@ -24,7 +24,7 @@ use flowy_user::entities::{
};
use flowy_user::errors::{FlowyError, FlowyResult};
use flowy_user::event_map::UserEvent;
use lib_dispatch::prelude::{af_spawn, AFPluginDispatcher, AFPluginRequest, ToBytes};
use lib_dispatch::prelude::{AFPluginDispatcher, AFPluginRequest, ToBytes};
use crate::event_builder::EventBuilder;
use crate::EventIntegrationTest;
@ -328,7 +328,7 @@ impl TestNotificationSender {
let (tx, rx) = tokio::sync::mpsc::channel::<T>(10);
let mut receiver = self.sender.subscribe();
let ty = ty.into();
af_spawn(async move {
tokio::spawn(async move {
// DatabaseNotification::DidUpdateDatabaseSnapshotState
while let Ok(value) = receiver.recv().await {
if value.id == id && value.ty == ty {
@ -361,7 +361,7 @@ impl TestNotificationSender {
let (tx, rx) = tokio::sync::mpsc::channel::<()>(10);
let mut receiver = self.sender.subscribe();
let ty = ty.into();
af_spawn(async move {
tokio::spawn(async move {
// DatabaseNotification::DidUpdateDatabaseSnapshotState
while let Ok(value) = receiver.recv().await {
if value.id == id && value.ty == ty {
@ -380,7 +380,7 @@ impl TestNotificationSender {
let id = id.to_string();
let (tx, rx) = tokio::sync::mpsc::channel::<T>(1);
let mut receiver = self.sender.subscribe();
af_spawn(async move {
tokio::spawn(async move {
while let Ok(value) = receiver.recv().await {
if value.id == id {
if let Some(payload) = value.payload {

View File

@ -1,7 +1,7 @@
use crate::util::unzip;
use event_integration_test::EventIntegrationTest;
use flowy_core::DEFAULT_NAME;
use flowy_folder::entities::{ImportPayloadPB, ImportTypePB, ImportValuePayloadPB, ViewLayoutPB};
use flowy_folder::entities::{ImportItemPayloadPB, ImportPayloadPB, ImportTypePB, ViewLayoutPB};
#[tokio::test]
async fn import_492_row_csv_file_test() {
@ -47,7 +47,7 @@ async fn import_10240_row_csv_file_test() {
fn gen_import_data(file_name: String, csv_string: String, workspace_id: String) -> ImportPayloadPB {
ImportPayloadPB {
parent_view_id: workspace_id.clone(),
values: vec![ImportValuePayloadPB {
items: vec![ImportItemPayloadPB {
name: file_name,
data: Some(csv_string.as_bytes().to_vec()),
file_path: None,

View File

@ -3,9 +3,9 @@ use std::collections::HashMap;
use collab_folder::ViewLayout;
use event_integration_test::EventIntegrationTest;
use flowy_folder::entities::{
ImportPayloadPB, ImportTypePB, ImportValuePayloadPB, ViewLayoutPB, ViewPB,
ImportItemPayloadPB, ImportPayloadPB, ImportTypePB, ViewLayoutPB, ViewPB,
};
use flowy_folder::view_operation::EncodedCollabWrapper;
use flowy_folder::view_operation::EncodedCollabType;
use crate::util::unzip;
@ -22,7 +22,7 @@ async fn publish_single_database_test() {
.await;
match grid_encoded_collab {
EncodedCollabWrapper::Database(encoded_collab) => {
EncodedCollabType::Database(encoded_collab) => {
// the len of row collabs should be the same as the number of rows in the csv file
let rows_len = encoded_collab.database_row_encoded_collabs.len();
assert_eq!(rows_len, 18);
@ -110,7 +110,7 @@ async fn test_publish_encode_collab_result(
.await;
match encoded_collab {
EncodedCollabWrapper::Database(encoded_collab) => {
EncodedCollabType::Database(encoded_collab) => {
if let Some(rows_len) = expectations.get(&view.name.as_str()) {
assert_eq!(encoded_collab.database_row_encoded_collabs.len(), *rows_len);
}
@ -144,7 +144,7 @@ async fn import_csv(file_name: &str, test: &EventIntegrationTest) -> ViewPB {
fn gen_import_data(file_name: String, csv_string: String, workspace_id: String) -> ImportPayloadPB {
ImportPayloadPB {
parent_view_id: workspace_id.clone(),
values: vec![ImportValuePayloadPB {
items: vec![ImportItemPayloadPB {
name: file_name,
data: Some(csv_string.as_bytes().to_vec()),
file_path: None,

View File

@ -2,7 +2,7 @@ use collab_folder::ViewLayout;
use event_integration_test::EventIntegrationTest;
use flowy_folder::entities::{ViewLayoutPB, ViewPB};
use flowy_folder::publish_util::generate_publish_name;
use flowy_folder::view_operation::EncodedCollabWrapper;
use flowy_folder::view_operation::EncodedCollabType;
use flowy_folder_pub::entities::{
PublishDocumentPayload, PublishPayload, PublishViewInfo, PublishViewMeta, PublishViewMetaData,
};
@ -29,7 +29,7 @@ async fn mock_single_document_view_publish_payload(
};
let data = match view_encoded_collab {
EncodedCollabWrapper::Document(doc) => doc.document_encoded_collab.doc_state.to_vec(),
EncodedCollabType::Document(doc) => doc.document_encoded_collab.doc_state.to_vec(),
_ => panic!("Expected document collab"),
};
@ -89,12 +89,12 @@ async fn mock_nested_document_view_publish_payload(
let child_publish_name = generate_publish_name(&child_view.id, &child_view.name);
let data = match view_encoded_collab {
EncodedCollabWrapper::Document(doc) => doc.document_encoded_collab.doc_state.to_vec(),
EncodedCollabType::Document(doc) => doc.document_encoded_collab.doc_state.to_vec(),
_ => panic!("Expected document collab"),
};
let child_data = match child_view_encoded_collab {
EncodedCollabWrapper::Document(doc) => doc.document_encoded_collab.doc_state.to_vec(),
EncodedCollabType::Document(doc) => doc.document_encoded_collab.doc_state.to_vec(),
_ => panic!("Expected document collab"),
};

View File

@ -5,8 +5,8 @@ pub use client_api::entity::ai_dto::{
};
pub use client_api::entity::billing_dto::SubscriptionPlan;
pub use client_api::entity::chat_dto::{
ChatMessage, ChatMessageMetadata, ChatMessageType, ChatRAGData, ContextLoader, MessageCursor,
RepeatedChatMessage,
ChatMessage, ChatMessageMetadata, ChatMessageType, ChatRAGData, ChatSettings, ContextLoader,
MessageCursor, RepeatedChatMessage, UpdateChatParams,
};
pub use client_api::entity::QuestionStreamValue;
use client_api::error::AppResponseError;
@ -27,6 +27,7 @@ pub trait ChatCloudService: Send + Sync + 'static {
uid: &i64,
workspace_id: &str,
chat_id: &str,
rag_ids: Vec<String>,
) -> Result<(), FlowyError>;
async fn create_question(
@ -97,4 +98,17 @@ pub trait ChatCloudService: Send + Sync + 'static {
&self,
workspace_id: &str,
) -> Result<Vec<SubscriptionPlan>, FlowyError>;
async fn get_chat_settings(
&self,
workspace_id: &str,
chat_id: &str,
) -> Result<ChatSettings, FlowyError>;
async fn update_chat_settings(
&self,
workspace_id: &str,
chat_id: &str,
params: UpdateChatParams,
) -> Result<(), FlowyError>;
}

View File

@ -20,21 +20,4 @@ fn main() {
flowy_codegen::Project::TauriApp,
);
}
#[cfg(feature = "web_ts")]
{
flowy_codegen::ts_event::gen(
"folder",
flowy_codegen::Project::Web {
relative_path: "../../".to_string(),
},
);
flowy_codegen::protobuf_file::ts_gen(
env!("CARGO_PKG_NAME"),
"folder",
flowy_codegen::Project::Web {
relative_path: "../../".to_string(),
},
);
}
}

View File

@ -8,12 +8,15 @@ use crate::persistence::{insert_chat, read_chat_metadata, ChatTable};
use appflowy_plugin::manager::PluginManager;
use dashmap::DashMap;
use flowy_ai_pub::cloud::{ChatCloudService, ChatMessageMetadata, ChatMessageType};
use flowy_ai_pub::cloud::{
ChatCloudService, ChatMessageMetadata, ChatMessageType, UpdateChatParams,
};
use flowy_error::{FlowyError, FlowyResult};
use flowy_sqlite::kv::KVStorePreferences;
use flowy_sqlite::DBConnection;
use flowy_storage_pub::storage::StorageService;
use lib_infra::async_trait::async_trait;
use lib_infra::util::timestamp;
use std::path::PathBuf;
use std::sync::{Arc, Weak};
@ -27,9 +30,19 @@ pub trait AIUserService: Send + Sync + 'static {
fn application_root_dir(&self) -> Result<PathBuf, FlowyError>;
}
#[async_trait]
pub trait AIQueryService: Send + Sync + 'static {
async fn query_chat_rag_ids(
&self,
parent_view_id: &str,
chat_id: &str,
) -> Result<Vec<String>, FlowyError>;
}
pub struct AIManager {
pub cloud_service_wm: Arc<AICloudServiceMiddleware>,
pub user_service: Arc<dyn AIUserService>,
pub query_service: Arc<dyn AIQueryService>,
chats: Arc<DashMap<String, Arc<Chat>>>,
pub local_ai_controller: Arc<LocalAIController>,
}
@ -40,6 +53,7 @@ impl AIManager {
user_service: impl AIUserService,
store_preferences: Arc<KVStorePreferences>,
storage_service: Weak<dyn StorageService>,
query_service: impl AIQueryService,
) -> AIManager {
let user_service = Arc::new(user_service);
let plugin_manager = Arc::new(PluginManager::new());
@ -49,6 +63,7 @@ impl AIManager {
user_service.clone(),
chat_cloud_service.clone(),
));
let query_service = Arc::new(query_service);
// setup local chat service
let cloud_service_wm = Arc::new(AICloudServiceMiddleware::new(
@ -63,6 +78,7 @@ impl AIManager {
user_service,
chats: Arc::new(DashMap::new()),
local_ai_controller,
query_service,
}
}
@ -73,7 +89,6 @@ impl AIManager {
}
pub async fn open_chat(&self, chat_id: &str) -> Result<(), FlowyError> {
trace!("open chat: {}", chat_id);
self.chats.entry(chat_id.to_string()).or_insert_with(|| {
Arc::new(Chat::new(
self.user_service.user_id().unwrap(),
@ -126,11 +141,23 @@ impl AIManager {
})
}
pub async fn create_chat(&self, uid: &i64, chat_id: &str) -> Result<Arc<Chat>, FlowyError> {
pub async fn create_chat(
&self,
uid: &i64,
parent_view_id: &str,
chat_id: &str,
) -> Result<Arc<Chat>, FlowyError> {
let workspace_id = self.user_service.workspace_id()?;
let rag_ids = self
.query_service
.query_chat_rag_ids(parent_view_id, chat_id)
.await
.unwrap_or_default();
info!("[Chat] create chat with rag_ids: {:?}", rag_ids);
self
.cloud_service_wm
.create_chat(uid, &workspace_id, chat_id)
.create_chat(uid, &workspace_id, chat_id, rag_ids)
.await?;
save_chat(self.user_service.sqlite_connection(*uid)?, chat_id)?;
@ -257,6 +284,22 @@ impl AIManager {
}
pub fn local_ai_purchased(&self) {}
pub async fn update_rag_ids(&self, chat_id: &str, rag_ids: Vec<String>) -> FlowyResult<()> {
if !rag_ids.is_empty() {
let workspace_id = self.user_service.workspace_id()?;
let update_setting = UpdateChatParams {
name: None,
metadata: None,
rag_ids: Some(rag_ids),
};
self
.cloud_service_wm
.update_chat_settings(&workspace_id, chat_id, update_setting)
.await?;
}
Ok(())
}
}
fn save_chat(conn: DBConnection, chat_id: &str) -> FlowyResult<()> {

View File

@ -3,7 +3,7 @@ use crate::entities::{
ChatMessageErrorPB, ChatMessageListPB, ChatMessagePB, RepeatedRelatedQuestionPB,
};
use crate::middleware::chat_service_mw::AICloudServiceMiddleware;
use crate::notification::{make_notification, ChatNotification};
use crate::notification::{chat_notification_builder, ChatNotification};
use crate::persistence::{insert_chat_messages, select_chat_messages, ChatMessageTable};
use crate::stream_message::StreamMessage;
use allo_isolate::Isolate;
@ -86,10 +86,6 @@ impl Chat {
question_stream_port: i64,
metadata: Vec<ChatMessageMetadata>,
) -> Result<ChatMessagePB, FlowyError> {
if message.len() > 2000 {
return Err(FlowyError::text_too_long().with_context("Exceeds maximum message 2000 length"));
}
trace!(
"[Chat] stream chat message: chat_id={}, message={}, message_type={:?}, metadata={:?}",
self.chat_id,
@ -181,7 +177,7 @@ impl Chat {
chat_id: chat_id.clone(),
error_message: err.to_string(),
};
make_notification(&chat_id, ChatNotification::StreamChatMessageError)
chat_notification_builder(&chat_id, ChatNotification::StreamChatMessageError)
.payload(pb)
.send();
return Err(err);
@ -201,14 +197,14 @@ impl Chat {
chat_id: chat_id.clone(),
error_message: err.to_string(),
};
make_notification(&chat_id, ChatNotification::StreamChatMessageError)
chat_notification_builder(&chat_id, ChatNotification::StreamChatMessageError)
.payload(pb)
.send();
return Err(err);
},
}
make_notification(&chat_id, ChatNotification::FinishStreaming).send();
chat_notification_builder(&chat_id, ChatNotification::FinishStreaming).send();
if answer_stream_buffer.lock().await.is_empty() {
return Ok(());
}
@ -260,7 +256,7 @@ impl Chat {
has_more: true,
total: 0,
};
make_notification(&self.chat_id, ChatNotification::DidLoadPrevChatMessage)
chat_notification_builder(&self.chat_id, ChatNotification::DidLoadPrevChatMessage)
.payload(pb.clone())
.send();
return Ok(pb);
@ -381,11 +377,11 @@ impl Chat {
} else {
*prev_message_state.write().await = PrevMessageState::NoMore;
}
make_notification(&chat_id, ChatNotification::DidLoadPrevChatMessage)
chat_notification_builder(&chat_id, ChatNotification::DidLoadPrevChatMessage)
.payload(pb)
.send();
} else {
make_notification(&chat_id, ChatNotification::DidLoadLatestChatMessage)
chat_notification_builder(&chat_id, ChatNotification::DidLoadLatestChatMessage)
.payload(pb)
.send();
}
@ -571,7 +567,7 @@ pub(crate) fn save_and_notify_message(
vec![message.clone()],
)?;
let pb = ChatMessagePB::from(message);
make_notification(chat_id, ChatNotification::DidReceiveChatMessage)
chat_notification_builder(chat_id, ChatNotification::DidReceiveChatMessage)
.payload(pb)
.send();

View File

@ -83,16 +83,16 @@ pub struct ChatMessageMetaPB {
pub data: String,
#[pb(index = 4)]
pub data_type: ChatMessageMetaTypePB,
pub loader_type: ContextLoaderTypePB,
#[pb(index = 5)]
pub source: String,
}
#[derive(Debug, Default, Clone, ProtoBuf_Enum, PartialEq, Eq, Copy)]
pub enum ChatMessageMetaTypePB {
pub enum ContextLoaderTypePB {
#[default]
UnknownMetaType = 0,
UnknownLoaderType = 0,
Txt = 1,
Markdown = 2,
PDF = 3,

View File

@ -5,7 +5,9 @@ use crate::ai_manager::AIManager;
use crate::completion::AICompletion;
use crate::entities::*;
use crate::local_ai::local_llm_chat::LLMModelInfo;
use crate::notification::{make_notification, ChatNotification, APPFLOWY_AI_NOTIFICATION_KEY};
use crate::notification::{
chat_notification_builder, ChatNotification, APPFLOWY_AI_NOTIFICATION_KEY,
};
use allo_isolate::Isolate;
use flowy_ai_pub::cloud::{ChatMessageMetadata, ChatMessageType, ChatRAGData, ContextLoader};
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
@ -34,15 +36,16 @@ pub(crate) async fn stream_chat_message_handler(
ChatMessageTypePB::System => ChatMessageType::System,
ChatMessageTypePB::User => ChatMessageType::User,
};
let metadata = data
.metadata
.into_iter()
.map(|metadata| {
let (content_type, content_len) = match metadata.data_type {
ChatMessageMetaTypePB::Txt => (ContextLoader::Text, metadata.data.len()),
ChatMessageMetaTypePB::Markdown => (ContextLoader::Markdown, metadata.data.len()),
ChatMessageMetaTypePB::PDF => (ContextLoader::PDF, 0),
ChatMessageMetaTypePB::UnknownMetaType => (ContextLoader::Unknown, 0),
let (content_type, content_len) = match metadata.loader_type {
ContextLoaderTypePB::Txt => (ContextLoader::Text, metadata.data.len()),
ContextLoaderTypePB::Markdown => (ContextLoader::Markdown, metadata.data.len()),
ContextLoaderTypePB::PDF => (ContextLoader::PDF, 0),
ContextLoaderTypePB::UnknownLoaderType => (ContextLoader::Unknown, 0),
};
ChatMessageMetadata {
@ -298,7 +301,7 @@ pub(crate) async fn toggle_local_ai_chat_handler(
file_enabled,
plugin_state,
};
make_notification(
chat_notification_builder(
APPFLOWY_AI_NOTIFICATION_KEY,
ChatNotification::UpdateLocalChatAI,
)
@ -323,7 +326,7 @@ pub(crate) async fn toggle_local_ai_chat_file_handler(
file_enabled,
plugin_state,
};
make_notification(
chat_notification_builder(
APPFLOWY_AI_NOTIFICATION_KEY,
ChatNotification::UpdateLocalChatAI,
)

View File

@ -1,7 +1,9 @@
use crate::ai_manager::AIUserService;
use crate::entities::{LocalAIPluginStatePB, LocalModelResourcePB, RunningStatePB};
use crate::local_ai::local_llm_resource::{LLMResourceService, LocalAIResourceController};
use crate::notification::{make_notification, ChatNotification, APPFLOWY_AI_NOTIFICATION_KEY};
use crate::notification::{
chat_notification_builder, ChatNotification, APPFLOWY_AI_NOTIFICATION_KEY,
};
use anyhow::Error;
use appflowy_local_ai::chat_plugin::{AIPluginConfig, AppFlowyLocalAI};
use appflowy_plugin::manager::PluginManager;
@ -90,7 +92,7 @@ impl LocalAIController {
info!("[AI Plugin] state: {:?}", state);
let offline_ai_ready = cloned_llm_res.is_offline_app_ready();
let new_state = RunningStatePB::from(state);
make_notification(
chat_notification_builder(
APPFLOWY_AI_NOTIFICATION_KEY,
ChatNotification::UpdateChatPluginState,
)
@ -590,12 +592,6 @@ impl LLMResourceService for LLMResourceServiceImpl {
.store_preferences
.get_object::<LLMSetting>(LOCAL_AI_SETTING_KEY)
}
fn is_rag_enabled(&self) -> bool {
self
.store_preferences
.get_bool_or_default(APPFLOWY_LOCAL_AI_CHAT_RAG_ENABLED)
}
}
fn local_ai_enabled_key(workspace_id: &str) -> String {

View File

@ -28,7 +28,6 @@ pub trait LLMResourceService: Send + Sync + 'static {
async fn fetch_local_ai_config(&self) -> Result<LocalAIConfig, anyhow::Error>;
fn store_setting(&self, setting: LLMSetting) -> Result<(), anyhow::Error>;
fn retrieve_setting(&self) -> Option<LLMSetting>;
fn is_rag_enabled(&self) -> bool;
}
const LLM_MODEL_DIR: &str = "models";

View File

@ -1,15 +1,17 @@
use crate::ai_manager::AIUserService;
use crate::entities::{ChatStatePB, ModelTypePB};
use crate::local_ai::local_llm_chat::LocalAIController;
use crate::notification::{make_notification, ChatNotification, APPFLOWY_AI_NOTIFICATION_KEY};
use crate::notification::{
chat_notification_builder, ChatNotification, APPFLOWY_AI_NOTIFICATION_KEY,
};
use crate::persistence::{select_single_message, ChatMessageTable};
use appflowy_plugin::error::PluginError;
use std::collections::HashMap;
use flowy_ai_pub::cloud::{
ChatCloudService, ChatMessage, ChatMessageMetadata, ChatMessageType, CompletionType,
LocalAIConfig, MessageCursor, RelatedQuestion, RepeatedChatMessage, RepeatedRelatedQuestion,
StreamAnswer, StreamComplete, SubscriptionPlan,
ChatCloudService, ChatMessage, ChatMessageMetadata, ChatMessageType, ChatSettings,
CompletionType, LocalAIConfig, MessageCursor, RelatedQuestion, RepeatedChatMessage,
RepeatedRelatedQuestion, StreamAnswer, StreamComplete, SubscriptionPlan, UpdateChatParams,
};
use flowy_error::{FlowyError, FlowyResult};
use futures::{stream, Sink, StreamExt, TryStreamExt};
@ -92,7 +94,7 @@ impl AICloudServiceMiddleware {
err,
PluginError::PluginNotConnected | PluginError::PeerDisconnect
) {
make_notification(
chat_notification_builder(
APPFLOWY_AI_NOTIFICATION_KEY,
ChatNotification::UpdateChatPluginState,
)
@ -112,10 +114,11 @@ impl ChatCloudService for AICloudServiceMiddleware {
uid: &i64,
workspace_id: &str,
chat_id: &str,
rag_ids: Vec<String>,
) -> Result<(), FlowyError> {
self
.cloud_service
.create_chat(uid, workspace_id, chat_id)
.create_chat(uid, workspace_id, chat_id, rag_ids)
.await
}
@ -314,4 +317,27 @@ impl ChatCloudService for AICloudServiceMiddleware {
) -> Result<Vec<SubscriptionPlan>, FlowyError> {
self.cloud_service.get_workspace_plan(workspace_id).await
}
async fn get_chat_settings(
&self,
workspace_id: &str,
chat_id: &str,
) -> Result<ChatSettings, FlowyError> {
self
.cloud_service
.get_chat_settings(workspace_id, chat_id)
.await
}
async fn update_chat_settings(
&self,
workspace_id: &str,
chat_id: &str,
params: UpdateChatParams,
) -> Result<(), FlowyError> {
self
.cloud_service
.update_chat_settings(workspace_id, chat_id, params)
.await
}
}

View File

@ -37,6 +37,6 @@ impl std::convert::From<i32> for ChatNotification {
}
#[tracing::instrument(level = "trace")]
pub(crate) fn make_notification(id: &str, ty: ChatNotification) -> NotificationBuilder {
pub(crate) fn chat_notification_builder(id: &str, ty: ChatNotification) -> NotificationBuilder {
NotificationBuilder::new(id, ty, CHAT_OBSERVABLE_SOURCE)
}

View File

@ -1,10 +1,12 @@
use flowy_ai::ai_manager::{AIManager, AIUserService};
use flowy_ai::ai_manager::{AIManager, AIQueryService, AIUserService};
use flowy_ai_pub::cloud::ChatCloudService;
use flowy_error::FlowyError;
use flowy_folder_pub::query::FolderQueryService;
use flowy_sqlite::kv::KVStorePreferences;
use flowy_sqlite::DBConnection;
use flowy_storage_pub::storage::StorageService;
use flowy_user::services::authenticate_user::AuthenticateUser;
use lib_infra::async_trait::async_trait;
use std::path::PathBuf;
use std::sync::{Arc, Weak};
@ -16,6 +18,7 @@ impl ChatDepsResolver {
cloud_service: Arc<dyn ChatCloudService>,
store_preferences: Arc<KVStorePreferences>,
storage_service: Weak<dyn StorageService>,
folder_query: impl FolderQueryService,
) -> Arc<AIManager> {
let user_service = ChatUserServiceImpl(authenticate_user);
Arc::new(AIManager::new(
@ -23,10 +26,34 @@ impl ChatDepsResolver {
user_service,
store_preferences,
storage_service,
ChatQueryServiceImpl {
folder_query: Box::new(folder_query),
},
))
}
}
struct ChatQueryServiceImpl {
folder_query: Box<dyn FolderQueryService>,
}
#[async_trait]
impl AIQueryService for ChatQueryServiceImpl {
async fn query_chat_rag_ids(
&self,
parent_view_id: &str,
chat_id: &str,
) -> Result<Vec<String>, FlowyError> {
let mut ids = self.folder_query.get_sibling_ids(parent_view_id).await;
if !ids.is_empty() {
ids.retain(|id| id != chat_id);
}
Ok(ids)
}
}
struct ChatUserServiceImpl(Weak<AuthenticateUser>);
impl ChatUserServiceImpl {
fn upgrade_user(&self) -> Result<Arc<AuthenticateUser>, FlowyError> {

View File

@ -17,8 +17,8 @@ use flowy_folder::entities::{CreateViewParams, ViewLayoutPB};
use flowy_folder::manager::{FolderManager, FolderUser};
use flowy_folder::share::ImportType;
use flowy_folder::view_operation::{
DatabaseEncodedCollab, DocumentEncodedCollab, EncodedCollabWrapper, FolderOperationHandler,
FolderOperationHandlers, ImportedData, View, ViewData,
DatabaseEncodedCollab, DocumentEncodedCollab, EncodedCollabType, FolderOperationHandler,
ImportedData, View, ViewData,
};
use flowy_folder::ViewLayout;
use flowy_search::folder::indexer::FolderIndexManagerImpl;
@ -34,6 +34,7 @@ use tokio::sync::RwLock;
use crate::integrate::server::ServerProvider;
use collab_plugins::local_storage::kv::KVTransactionDB;
use flowy_folder_pub::query::FolderQueryService;
use lib_infra::async_trait::async_trait;
pub struct FolderDepsResolver();
@ -45,7 +46,6 @@ impl FolderDepsResolver {
server_provider: Arc<ServerProvider>,
folder_indexer: Arc<FolderIndexManagerImpl>,
store_preferences: Arc<KVStorePreferences>,
operation_handlers: FolderOperationHandlers,
) -> Arc<FolderManager> {
let user: Arc<dyn FolderUser> = Arc::new(FolderUserImpl {
authenticate_user: authenticate_user.clone(),
@ -55,7 +55,6 @@ impl FolderDepsResolver {
FolderManager::new(
user.clone(),
collab_builder,
operation_handlers,
server_provider.clone(),
folder_indexer,
store_preferences,
@ -65,23 +64,21 @@ impl FolderDepsResolver {
}
}
pub fn folder_operation_handlers(
pub fn register_handlers(
folder_manager: &Arc<FolderManager>,
document_manager: Arc<DocumentManager>,
database_manager: Arc<DatabaseManager>,
chat_manager: Arc<AIManager>,
) -> FolderOperationHandlers {
let mut map: HashMap<ViewLayout, Arc<dyn FolderOperationHandler + Send + Sync>> = HashMap::new();
) {
let document_folder_operation = Arc::new(DocumentFolderOperation(document_manager));
map.insert(ViewLayout::Document, document_folder_operation);
folder_manager.register_operation_handler(ViewLayout::Document, document_folder_operation);
let database_folder_operation = Arc::new(DatabaseFolderOperation(database_manager));
let chat_folder_operation = Arc::new(ChatFolderOperation(chat_manager));
map.insert(ViewLayout::Board, database_folder_operation.clone());
map.insert(ViewLayout::Grid, database_folder_operation.clone());
map.insert(ViewLayout::Calendar, database_folder_operation);
map.insert(ViewLayout::Chat, chat_folder_operation);
Arc::new(map)
folder_manager.register_operation_handler(ViewLayout::Board, database_folder_operation.clone());
folder_manager.register_operation_handler(ViewLayout::Grid, database_folder_operation.clone());
folder_manager.register_operation_handler(ViewLayout::Calendar, database_folder_operation);
folder_manager.register_operation_handler(ViewLayout::Chat, chat_folder_operation);
}
struct FolderUserImpl {
@ -190,11 +187,33 @@ impl FolderOperationHandler for DocumentFolderOperation {
Ok(Some(encoded_collab))
}
/// Create a view with built-in data.
async fn create_default_view(
&self,
user_id: i64,
_parent_view_id: &str,
view_id: &str,
_name: &str,
layout: ViewLayout,
) -> Result<(), FlowyError> {
debug_assert_eq!(layout, ViewLayout::Document);
match self.0.create_document(user_id, view_id, None).await {
Ok(_) => Ok(()),
Err(err) => {
if err.is_already_exists() {
Ok(())
} else {
Err(err)
}
},
}
}
async fn get_encoded_collab_v1_from_disk(
&self,
user: Arc<dyn FolderUser>,
user: &Arc<dyn FolderUser>,
view_id: &str,
) -> Result<EncodedCollabWrapper, FlowyError> {
) -> Result<EncodedCollabType, FlowyError> {
// get the collab_object_id for the document.
// the collab_object_id for the document is the view_id.
let workspace_id = user.workspace_id()?;
@ -226,32 +245,11 @@ impl FolderOperationHandler for DocumentFolderOperation {
FlowyError::internal().with_context(format!("encode document collab failed: {}", e))
})?;
Ok(EncodedCollabWrapper::Document(DocumentEncodedCollab {
Ok(EncodedCollabType::Document(DocumentEncodedCollab {
document_encoded_collab: encoded_collab,
}))
}
/// Create a view with built-in data.
async fn create_view_with_default_data(
&self,
user_id: i64,
view_id: &str,
_name: &str,
layout: ViewLayout,
) -> Result<(), FlowyError> {
debug_assert_eq!(layout, ViewLayout::Document);
match self.0.create_document(user_id, view_id, None).await {
Ok(_) => Ok(()),
Err(err) => {
if err.is_already_exists() {
Ok(())
} else {
Err(err)
}
},
}
}
async fn import_from_bytes(
&self,
uid: i64,
@ -272,13 +270,13 @@ impl FolderOperationHandler for DocumentFolderOperation {
)])
}
// will implement soon
async fn import_from_file_path(
&self,
_view_id: &str,
_name: &str,
_path: String,
) -> Result<(), FlowyError> {
// TODO(lucas): import file from local markdown file
Ok(())
}
@ -311,9 +309,9 @@ impl FolderOperationHandler for DatabaseFolderOperation {
async fn get_encoded_collab_v1_from_disk(
&self,
user: Arc<dyn FolderUser>,
user: &Arc<dyn FolderUser>,
view_id: &str,
) -> Result<EncodedCollabWrapper, FlowyError> {
) -> Result<EncodedCollabType, FlowyError> {
let workspace_id = user.workspace_id()?;
// get the collab_object_id for the database.
//
@ -405,7 +403,7 @@ impl FolderOperationHandler for DatabaseFolderOperation {
})
.collect::<Result<HashMap<_, _>, FlowyError>>()?;
Ok(EncodedCollabWrapper::Database(DatabaseEncodedCollab {
Ok(EncodedCollabType::Database(DatabaseEncodedCollab {
database_encoded_collab,
database_row_encoded_collabs,
database_row_document_encoded_collabs,
@ -478,9 +476,10 @@ impl FolderOperationHandler for DatabaseFolderOperation {
/// If the ext contains the {"database_id": "xx"}, then it will link to
/// the existing database. The data of the database will be shared within
/// these references views.
async fn create_view_with_default_data(
async fn create_default_view(
&self,
_user_id: i64,
_parent_view_id: &str,
view_id: &str,
name: &str,
layout: ViewLayout,
@ -551,7 +550,10 @@ impl FolderOperationHandler for DatabaseFolderOperation {
_name: &str,
path: String,
) -> Result<(), FlowyError> {
self.0.import_csv_from_file(path, CSVFormat::META).await?;
self
.0
.import_csv_from_file(path, CSVFormat::Original)
.await?;
Ok(())
}
@ -625,14 +627,18 @@ impl FolderOperationHandler for ChatFolderOperation {
Err(FlowyError::not_support())
}
async fn create_view_with_default_data(
async fn create_default_view(
&self,
user_id: i64,
parent_view_id: &str,
view_id: &str,
_name: &str,
_layout: ViewLayout,
) -> Result<(), FlowyError> {
self.0.create_chat(&user_id, view_id).await?;
self
.0
.create_chat(&user_id, parent_view_id, view_id)
.await?;
Ok(())
}
@ -656,3 +662,36 @@ impl FolderOperationHandler for ChatFolderOperation {
Err(FlowyError::not_support())
}
}
#[derive(Clone, Debug)]
pub struct FolderQueryServiceImpl {
folder_manager: Weak<FolderManager>,
}
impl FolderQueryServiceImpl {
pub fn new(folder_manager: Weak<FolderManager>) -> Self {
Self { folder_manager }
}
}
#[async_trait]
impl FolderQueryService for FolderQueryServiceImpl {
async fn get_sibling_ids(&self, parent_view_id: &str) -> Vec<String> {
match self.folder_manager.upgrade() {
None => vec![],
Some(folder_manager) => {
if let Ok(parent_view) = folder_manager.get_view(parent_view_id).await {
if parent_view.space_info().is_none() {
if let Ok(views) = folder_manager.get_views_belong_to(parent_view_id).await {
return views
.into_iter()
.map(|child| child.id.clone())
.collect::<Vec<String>>();
}
}
}
vec![]
},
}
}
}

View File

@ -21,8 +21,9 @@ use collab_integrate::collab_builder::{
CollabCloudPluginProvider, CollabPluginProviderContext, CollabPluginProviderType,
};
use flowy_ai_pub::cloud::{
ChatCloudService, ChatMessage, ChatMessageMetadata, ChatMessageType, LocalAIConfig,
ChatCloudService, ChatMessage, ChatMessageMetadata, ChatMessageType, ChatSettings, LocalAIConfig,
MessageCursor, RepeatedChatMessage, StreamAnswer, StreamComplete, SubscriptionPlan,
UpdateChatParams,
};
use flowy_database_pub::cloud::{
DatabaseAIService, DatabaseCloudService, DatabaseSnapshot, EncodeCollabByOid, SummaryRowContent,
@ -643,11 +644,12 @@ impl ChatCloudService for ServerProvider {
uid: &i64,
workspace_id: &str,
chat_id: &str,
rag_ids: Vec<String>,
) -> Result<(), FlowyError> {
let server = self.get_server();
server?
.chat_service()
.create_chat(uid, workspace_id, chat_id)
.create_chat(uid, workspace_id, chat_id, rag_ids)
.await
}
@ -786,6 +788,31 @@ impl ChatCloudService for ServerProvider {
.get_workspace_plan(workspace_id)
.await
}
async fn get_chat_settings(
&self,
workspace_id: &str,
chat_id: &str,
) -> Result<ChatSettings, FlowyError> {
self
.get_server()?
.chat_service()
.get_chat_settings(workspace_id, chat_id)
.await
}
async fn update_chat_settings(
&self,
workspace_id: &str,
chat_id: &str,
params: UpdateChatParams,
) -> Result<(), FlowyError> {
self
.get_server()?
.chat_service()
.update_chat_settings(workspace_id, chat_id, params)
.await
}
}
#[async_trait]

View File

@ -161,11 +161,27 @@ impl AppFlowyCore {
collab_builder
.set_snapshot_persistence(Arc::new(SnapshotDBImpl(Arc::downgrade(&authenticate_user))));
let folder_indexer = Arc::new(FolderIndexManagerImpl::new(Some(Arc::downgrade(
&authenticate_user,
))));
let folder_manager = FolderDepsResolver::resolve(
Arc::downgrade(&authenticate_user),
collab_builder.clone(),
server_provider.clone(),
folder_indexer.clone(),
store_preference.clone(),
)
.await;
let folder_query_service = FolderQueryServiceImpl::new(Arc::downgrade(&folder_manager));
let ai_manager = ChatDepsResolver::resolve(
Arc::downgrade(&authenticate_user),
server_provider.clone(),
store_preference.clone(),
Arc::downgrade(&storage_manager.storage_service),
folder_query_service.clone(),
);
let database_manager = DatabaseDepsResolver::resolve(
@ -186,26 +202,6 @@ impl AppFlowyCore {
Arc::downgrade(&storage_manager.storage_service),
);
let folder_indexer = Arc::new(FolderIndexManagerImpl::new(Some(Arc::downgrade(
&authenticate_user,
))));
let folder_operation_handlers = folder_operation_handlers(
document_manager.clone(),
database_manager.clone(),
ai_manager.clone(),
);
let folder_manager = FolderDepsResolver::resolve(
Arc::downgrade(&authenticate_user),
collab_builder.clone(),
server_provider.clone(),
folder_indexer.clone(),
store_preference.clone(),
folder_operation_handlers,
)
.await;
let user_manager = UserDepsResolver::resolve(
authenticate_user.clone(),
collab_builder.clone(),
@ -223,6 +219,14 @@ impl AppFlowyCore {
)
.await;
// Register the folder operation handlers
register_handlers(
&folder_manager,
document_manager.clone(),
database_manager.clone(),
ai_manager.clone(),
);
(
user_manager,
folder_manager,

View File

@ -29,8 +29,8 @@ pub struct DateFilterContent {
pub timestamp: Option<i64>,
}
impl ToString for DateFilterContent {
fn to_string(&self) -> String {
impl DateFilterContent {
pub fn to_json_string(&self) -> String {
serde_json::to_string(self).unwrap()
}
}

View File

@ -1,4 +1,6 @@
use crate::services::field::{CHECK, UNCHECK};
use collab_database::fields::checkbox_type_option::CheckboxTypeOption;
use collab_database::template::util::ToCellString;
use flowy_derive::ProtoBuf;
#[derive(Default, Debug, Clone, ProtoBuf)]
@ -13,6 +15,16 @@ impl CheckboxCellDataPB {
}
}
impl ToCellString for CheckboxCellDataPB {
fn to_cell_string(&self) -> String {
if self.is_checked {
CHECK.to_string()
} else {
UNCHECK.to_string()
}
}
}
#[derive(Debug, Clone, Default, ProtoBuf)]
pub struct CheckboxTypeOptionPB {
/// unused

View File

@ -6,7 +6,7 @@ use tokio::sync::oneshot;
use tracing::{info, instrument};
use flowy_error::{FlowyError, FlowyResult};
use lib_dispatch::prelude::{af_spawn, data_result_ok, AFPluginData, AFPluginState, DataResult};
use lib_dispatch::prelude::{data_result_ok, AFPluginData, AFPluginState, DataResult};
use crate::entities::*;
use crate::manager::DatabaseManager;
@ -1260,7 +1260,7 @@ pub(crate) async fn summarize_row_handler(
let data = data.into_inner();
let row_id = RowId::from(data.row_id);
let (tx, rx) = oneshot::channel();
af_spawn(async move {
tokio::spawn(async move {
let result = manager
.summarize_row(data.view_id, row_id, data.field_id)
.await;
@ -1279,7 +1279,7 @@ pub(crate) async fn translate_row_handler(
let data = data.try_into_inner()?;
let row_id = RowId::from(data.row_id);
let (tx, rx) = oneshot::channel();
af_spawn(async move {
tokio::spawn(async move {
let result = manager
.translate_row(data.view_id, row_id, data.field_id)
.await;

View File

@ -6,7 +6,7 @@ use collab::core::origin::CollabOrigin;
use collab::lock::RwLock;
use collab::preclude::Collab;
use collab_database::database::{Database, DatabaseData};
use collab_database::entity::{CreateDatabaseParams, CreateViewParams};
use collab_database::entity::{CreateDatabaseParams, CreateViewParams, EncodedDatabase};
use collab_database::error::DatabaseError;
use collab_database::fields::translate_type_option::TranslateTypeOption;
use collab_database::rows::RowId;
@ -31,7 +31,7 @@ use flowy_database_pub::cloud::{
DatabaseAIService, DatabaseCloudService, SummaryRowContent, TranslateItem, TranslateRowContent,
};
use flowy_error::{internal_error, FlowyError, FlowyResult};
use lib_dispatch::prelude::af_spawn;
use lib_infra::box_any::BoxAny;
use lib_infra::priority_task::TaskDispatcher;
@ -189,6 +189,17 @@ impl DatabaseManager {
})
}
pub async fn encode_database(&self, view_id: &str) -> FlowyResult<EncodedDatabase> {
let editor = self.get_database_editor_with_view_id(view_id).await?;
let collabs = editor
.database
.read()
.await
.encode_database_collabs()
.await?;
Ok(collabs)
}
pub async fn get_database_row_ids_with_view_id(&self, view_id: &str) -> FlowyResult<Vec<RowId>> {
let database = self.get_database_editor_with_view_id(view_id).await?;
Ok(database.get_row_ids().await)
@ -316,7 +327,7 @@ impl DatabaseManager {
let weak_workspace_database = Arc::downgrade(&self.workspace_database()?);
let weak_removing_editors = Arc::downgrade(&self.removing_editor);
af_spawn(async move {
tokio::spawn(async move {
tokio::time::sleep(std::time::Duration::from_secs(120)).await;
if let Some(removing_editors) = weak_removing_editors.upgrade() {
if removing_editors.lock().await.remove(&database_id).is_some() {
@ -891,6 +902,7 @@ impl DatabaseCollabService for WorkspaceDatabaseCollabServiceImpl {
"[Database]: load {} database row from local disk",
local_disk_encoded_collab.len()
);
object_ids.retain(|object_id| !local_disk_encoded_collab.contains_key(object_id));
for (k, v) in local_disk_encoded_collab {
encoded_collab_by_id.insert(k, v);

View File

@ -90,7 +90,7 @@ impl std::convert::From<i32> for DatabaseNotification {
}
#[tracing::instrument(level = "trace")]
pub fn send_notification(id: &str, ty: DatabaseNotification) -> NotificationBuilder {
pub fn database_notification_builder(id: &str, ty: DatabaseNotification) -> NotificationBuilder {
#[cfg(feature = "verbose_log")]
tracing::trace!("[Database Notification]: id:{}, ty:{:?}", id, ty);

View File

@ -86,7 +86,7 @@ impl CalculationsController {
let task = Task::new(
&self.handler_id,
task_id,
TaskContent::Text(task_type.to_string()),
TaskContent::Text(task_type.to_json_string()),
qos,
);
self.task_scheduler.write().await.add_task(task);
@ -322,7 +322,7 @@ impl CalculationsController {
) -> Vec<CalculationPB> {
let mut updates = vec![];
let update = self
.update_calculation(calculation, &field, field_cells)
.update_calculation(calculation, field, field_cells)
.await;
if let Some(update) = update {
updates.push(CalculationPB::from(&update));
@ -415,8 +415,8 @@ pub(crate) enum CalculationEvent {
FieldDeleted(String),
}
impl ToString for CalculationEvent {
fn to_string(&self) -> String {
impl CalculationEvent {
fn to_json_string(&self) -> String {
serde_json::to_string(self).unwrap()
}
}

View File

@ -285,7 +285,7 @@ impl<'a> CellBuilder<'a> {
}
pub fn insert_text_cell(&mut self, field_id: &str, data: String) {
match self.field_maps.get(&field_id.to_owned()) {
match self.field_maps.get(field_id) {
None => tracing::warn!("Can't find the text field with id: {}", field_id),
Some(field) => {
self
@ -296,7 +296,7 @@ impl<'a> CellBuilder<'a> {
}
pub fn insert_url_cell(&mut self, field_id: &str, data: String) {
match self.field_maps.get(&field_id.to_owned()) {
match self.field_maps.get(field_id) {
None => tracing::warn!("Can't find the url field with id: {}", field_id),
Some(field) => {
self
@ -307,7 +307,7 @@ impl<'a> CellBuilder<'a> {
}
pub fn insert_number_cell(&mut self, field_id: &str, num: i64) {
match self.field_maps.get(&field_id.to_owned()) {
match self.field_maps.get(field_id) {
None => tracing::warn!("Can't find the number field with id: {}", field_id),
Some(field) => {
self
@ -318,7 +318,7 @@ impl<'a> CellBuilder<'a> {
}
pub fn insert_checkbox_cell(&mut self, field_id: &str, is_checked: bool) {
match self.field_maps.get(&field_id.to_owned()) {
match self.field_maps.get(field_id) {
None => tracing::warn!("Can't find the checkbox field with id: {}", field_id),
Some(field) => {
self
@ -329,7 +329,7 @@ impl<'a> CellBuilder<'a> {
}
pub fn insert_date_cell(&mut self, field_id: &str, timestamp: i64, include_time: Option<bool>) {
match self.field_maps.get(&field_id.to_owned()) {
match self.field_maps.get(field_id) {
None => tracing::warn!("Can't find the date field with id: {}", field_id),
Some(field) => {
self.cells.insert(
@ -341,7 +341,7 @@ impl<'a> CellBuilder<'a> {
}
pub fn insert_select_option_cell(&mut self, field_id: &str, option_ids: Vec<String>) {
match self.field_maps.get(&field_id.to_owned()) {
match self.field_maps.get(field_id) {
None => tracing::warn!("Can't find the select option field with id: {}", field_id),
Some(field) => {
self.cells.insert(
@ -356,7 +356,7 @@ impl<'a> CellBuilder<'a> {
field_id: &str,
new_tasks: Vec<ChecklistCellInsertChangeset>,
) {
match self.field_maps.get(&field_id.to_owned()) {
match self.field_maps.get(field_id) {
None => tracing::warn!("Can't find the field with id: {}", field_id),
Some(field) => {
self

View File

@ -1,4 +1,5 @@
use bytes::Bytes;
use std::fmt::Display;
use flowy_error::{internal_error, FlowyResult};
@ -64,15 +65,10 @@ impl CellProtobufBlob {
// }
}
impl ToString for CellProtobufBlob {
fn to_string(&self) -> String {
match String::from_utf8(self.0.to_vec()) {
Ok(s) => s,
Err(e) => {
tracing::error!("DecodedCellData to string failed: {:?}", e);
"".to_string()
},
}
impl Display for CellProtobufBlob {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = String::from_utf8(self.0.to_vec()).unwrap_or_default();
write!(f, "{}", s)
}
}

View File

@ -1,5 +1,5 @@
use crate::entities::*;
use crate::notification::{send_notification, DatabaseNotification};
use crate::notification::{database_notification_builder, DatabaseNotification};
use crate::services::calculations::Calculation;
use crate::services::cell::{apply_cell_changeset, get_cell_protobuf, CellCache};
use crate::services::database::database_observe::*;
@ -226,7 +226,7 @@ impl DatabaseEditor {
let field_type = FieldType::from(field.field_type);
setting_content = group_config_pb_to_json_str(data, &field_type)?;
let mut group_setting = default_group_setting(&field);
group_setting.content = setting_content.clone();
group_setting.content.clone_from(&setting_content);
database.update_database_view(view_id, |view| {
view.set_groups(vec![group_setting.into()]);
});
@ -252,7 +252,7 @@ impl DatabaseEditor {
let changes = view_editor.v_delete_group(&params.group_id).await?;
if !changes.is_empty() {
for view in self.database_views.editors().await {
send_notification(&view.view_id, DatabaseNotification::DidUpdateRow)
database_notification_builder(&view.view_id, DatabaseNotification::DidUpdateRow)
.payload(changes.clone())
.send();
}
@ -762,7 +762,7 @@ impl DatabaseEditor {
updated_fields: vec![],
};
send_notification(&params.view_id, DatabaseNotification::DidUpdateFields)
database_notification_builder(&params.view_id, DatabaseNotification::DidUpdateFields)
.payload(notified_changeset)
.send();
}
@ -879,7 +879,7 @@ impl DatabaseEditor {
}
// Notifies the client that the row meta has been updated.
send_notification(row_id.as_str(), DatabaseNotification::DidUpdateRowMeta)
database_notification_builder(row_id.as_str(), DatabaseNotification::DidUpdateRowMeta)
.payload(RowMetaPB::from(row_detail))
.send();
}
@ -1403,7 +1403,7 @@ impl DatabaseEditor {
) -> FlowyResult<()> {
let views = self.database.read().await.get_all_database_views_meta();
for view in views {
send_notification(&view.id, DatabaseNotification::DidUpdateFields)
database_notification_builder(&view.id, DatabaseNotification::DidUpdateFields)
.payload(changeset.clone())
.send();
}
@ -2219,7 +2219,7 @@ impl DatabaseViewOperation for DatabaseViewOperationImpl {
new_field_settings.clone(),
);
send_notification(
database_notification_builder(
&params.view_id,
DatabaseNotification::DidUpdateFieldSettings,
)
@ -2290,12 +2290,12 @@ fn notify_did_update_database_field(database: &Database, field_id: &str) -> Flow
DatabaseFieldChangesetPB::update(&database_id, vec![updated_field.clone()]);
for view in views {
send_notification(&view.id, DatabaseNotification::DidUpdateFields)
database_notification_builder(&view.id, DatabaseNotification::DidUpdateFields)
.payload(notified_changeset.clone())
.send();
}
send_notification(field_id, DatabaseNotification::DidUpdateField)
database_notification_builder(field_id, DatabaseNotification::DidUpdateField)
.payload(updated_field)
.send();
}

View File

@ -1,5 +1,7 @@
use crate::entities::{DatabaseSyncStatePB, DidFetchRowPB, RowsChangePB};
use crate::notification::{send_notification, DatabaseNotification, DATABASE_OBSERVABLE_SOURCE};
use crate::notification::{
database_notification_builder, DatabaseNotification, DATABASE_OBSERVABLE_SOURCE,
};
use crate::services::database::{DatabaseEditor, UpdatedRow};
use crate::services::database_view::DatabaseViewEditor;
use collab::lock::RwLock;
@ -11,7 +13,7 @@ use collab_database::views::{DatabaseViewChange, RowOrder};
use dashmap::DashMap;
use flowy_notification::{DebounceNotificationSender, NotificationBuilder};
use futures::StreamExt;
use lib_dispatch::prelude::af_spawn;
use std::sync::Arc;
use tracing::{error, trace, warn};
@ -19,13 +21,13 @@ pub(crate) async fn observe_sync_state(database_id: &str, database: &Arc<RwLock<
let weak_database = Arc::downgrade(database);
let mut sync_state = database.read().await.subscribe_sync_state();
let database_id = database_id.to_string();
af_spawn(async move {
tokio::spawn(async move {
while let Some(sync_state) = sync_state.next().await {
if weak_database.upgrade().is_none() {
break;
}
send_notification(
database_notification_builder(
&database_id,
DatabaseNotification::DidUpdateDatabaseSyncUpdate,
)
@ -45,7 +47,7 @@ pub(crate) async fn observe_rows_change(
let weak_database = Arc::downgrade(database);
let sub = database.read().await.subscribe_row_change();
if let Some(mut row_change) = sub {
af_spawn(async move {
tokio::spawn(async move {
while let Ok(row_change) = row_change.recv().await {
trace!(
"[Database Observe]: {} row change:{:?}",
@ -88,7 +90,7 @@ pub(crate) async fn observe_field_change(database_id: &str, database: &Arc<RwLoc
let weak_database = Arc::downgrade(database);
let sub = database.read().await.subscribe_field_change();
if let Some(mut field_change) = sub {
af_spawn(async move {
tokio::spawn(async move {
while let Ok(field_change) = field_change.recv().await {
if weak_database.upgrade().is_none() {
break;
@ -120,7 +122,7 @@ pub(crate) async fn observe_view_change(database_id: &str, database_editor: &Arc
.subscribe_view_change();
if let Some(mut view_change) = view_change {
af_spawn(async move {
tokio::spawn(async move {
while let Ok(view_change) = view_change.recv().await {
trace!(
"[Database View Observe]: {} view change:{:?}",
@ -281,7 +283,7 @@ async fn handle_did_update_row_orders(
for entry in row_changes.into_iter() {
let (view_id, changes) = entry;
trace!("[RowOrder]: {}", changes);
send_notification(&view_id, DatabaseNotification::DidUpdateRow)
database_notification_builder(&view_id, DatabaseNotification::DidUpdateRow)
.payload(changes)
.send();
}
@ -295,7 +297,7 @@ pub(crate) async fn observe_block_event(database_id: &str, database_editor: &Arc
.await
.subscribe_block_event();
let database_editor = Arc::downgrade(database_editor);
af_spawn(async move {
tokio::spawn(async move {
while let Ok(event) = block_event_rx.recv().await {
if database_editor.upgrade().is_none() {
break;
@ -312,7 +314,7 @@ pub(crate) async fn observe_block_event(database_id: &str, database_editor: &Arc
trace!("Did fetch row: {:?}", row_detail.row.id);
let row_id = row_detail.row.id.clone();
let pb = DidFetchRowPB::from(row_detail);
send_notification(&row_id, DatabaseNotification::DidFetchRow)
database_notification_builder(&row_id, DatabaseNotification::DidFetchRow)
.payload(pb)
.send();
}

View File

@ -4,7 +4,7 @@ use crate::entities::{
GroupChangesPB, GroupRowsNotificationPB, ReorderAllRowsPB, ReorderSingleRowPB,
RowsVisibilityChangePB, SortChangesetNotificationPB,
};
use crate::notification::{send_notification, DatabaseNotification};
use crate::notification::{database_notification_builder, DatabaseNotification};
use crate::services::filter::FilterResultNotification;
use crate::services::sort::{ReorderAllRowsResult, ReorderSingleRowResult};
use async_stream::stream;
@ -50,7 +50,7 @@ impl DatabaseViewChangedReceiverRunner {
.collect(),
};
send_notification(
database_notification_builder(
&changeset.view_id,
DatabaseNotification::DidUpdateViewRowsVisibility,
)
@ -61,9 +61,12 @@ impl DatabaseViewChangedReceiverRunner {
let row_orders = ReorderAllRowsPB {
row_orders: notification.row_orders,
};
send_notification(&notification.view_id, DatabaseNotification::DidReorderRows)
.payload(row_orders)
.send()
database_notification_builder(
&notification.view_id,
DatabaseNotification::DidReorderRows,
)
.payload(row_orders)
.send()
},
DatabaseViewChanged::ReorderSingleRowNotification(notification) => {
let reorder_row = ReorderSingleRowPB {
@ -71,19 +74,21 @@ impl DatabaseViewChangedReceiverRunner {
old_index: notification.old_index as i32,
new_index: notification.new_index as i32,
};
send_notification(
database_notification_builder(
&notification.view_id,
DatabaseNotification::DidReorderSingleRow,
)
.payload(reorder_row)
.send()
},
DatabaseViewChanged::CalculationValueNotification(notification) => send_notification(
&notification.view_id,
DatabaseNotification::DidUpdateCalculation,
)
.payload(notification)
.send(),
DatabaseViewChanged::CalculationValueNotification(notification) => {
database_notification_builder(
&notification.view_id,
DatabaseNotification::DidUpdateCalculation,
)
.payload(notification)
.send()
},
}
})
.await;
@ -91,19 +96,19 @@ impl DatabaseViewChangedReceiverRunner {
}
pub async fn notify_did_update_group_rows(payload: GroupRowsNotificationPB) {
send_notification(&payload.group_id, DatabaseNotification::DidUpdateGroupRow)
database_notification_builder(&payload.group_id, DatabaseNotification::DidUpdateGroupRow)
.payload(payload)
.send();
}
pub async fn notify_did_update_filter(notification: FilterChangesetNotificationPB) {
send_notification(&notification.view_id, DatabaseNotification::DidUpdateFilter)
database_notification_builder(&notification.view_id, DatabaseNotification::DidUpdateFilter)
.payload(notification)
.send();
}
pub async fn notify_did_update_calculation(notification: CalculationChangesetNotificationPB) {
send_notification(
database_notification_builder(
&notification.view_id,
DatabaseNotification::DidUpdateCalculation,
)
@ -113,20 +118,20 @@ pub async fn notify_did_update_calculation(notification: CalculationChangesetNot
pub async fn notify_did_update_sort(notification: SortChangesetNotificationPB) {
if !notification.is_empty() {
send_notification(&notification.view_id, DatabaseNotification::DidUpdateSort)
database_notification_builder(&notification.view_id, DatabaseNotification::DidUpdateSort)
.payload(notification)
.send();
}
}
pub(crate) async fn notify_did_update_num_of_groups(view_id: &str, changeset: GroupChangesPB) {
send_notification(view_id, DatabaseNotification::DidUpdateNumOfGroups)
database_notification_builder(view_id, DatabaseNotification::DidUpdateNumOfGroups)
.payload(changeset)
.send();
}
pub(crate) async fn notify_did_update_setting(view_id: &str, setting: DatabaseViewSettingPB) {
send_notification(view_id, DatabaseNotification::DidUpdateSettings)
database_notification_builder(view_id, DatabaseNotification::DidUpdateSettings)
.payload(setting)
.send();
}

View File

@ -10,7 +10,7 @@ use crate::entities::{
RemoveCalculationChangesetPB, ReorderSortPayloadPB, RowMetaPB, RowsChangePB,
SortChangesetNotificationPB, SortPB, UpdateCalculationChangesetPB, UpdateSortPayloadPB,
};
use crate::notification::{send_notification, DatabaseNotification};
use crate::notification::{database_notification_builder, DatabaseNotification};
use crate::services::calculations::{Calculation, CalculationChangeset, CalculationsController};
use crate::services::cell::{CellBuilder, CellCache};
use crate::services::database::{database_view_setting_pb_from_view, DatabaseRowEvent, UpdatedRow};
@ -233,7 +233,7 @@ impl DatabaseViewEditor {
if rows.pop().is_some() {
let update_row = UpdatedRow::new(row_id.as_str()).with_row_meta(row_detail.clone());
let changeset = RowsChangePB::from_update(update_row.into());
send_notification(&self.view_id, DatabaseNotification::DidUpdateRow)
database_notification_builder(&self.view_id, DatabaseNotification::DidUpdateRow)
.payload(changeset)
.send();
}
@ -706,7 +706,7 @@ impl DatabaseViewEditor {
}))
.await
.into_iter()
.filter_map(|result| result)
.flatten()
.collect();
// Pre-compute cells by field ID only for fields that have calculations
@ -940,7 +940,7 @@ impl DatabaseViewEditor {
};
if let Some(payload) = layout_setting_pb {
send_notification(&self.view_id, DatabaseNotification::DidUpdateLayoutSettings)
database_notification_builder(&self.view_id, DatabaseNotification::DidUpdateLayoutSettings)
.payload(payload)
.send();
}
@ -1060,7 +1060,7 @@ impl DatabaseViewEditor {
debug_assert!(!changeset.is_empty());
if !changeset.is_empty() {
send_notification(&changeset.view_id, DatabaseNotification::DidGroupByField)
database_notification_builder(&changeset.view_id, DatabaseNotification::DidGroupByField)
.payload(changeset)
.send();
}
@ -1196,7 +1196,7 @@ impl DatabaseViewEditor {
view_id: self.view_id.clone(),
layout: new_layout_type.into(),
};
send_notification(&self.view_id, DatabaseNotification::DidUpdateDatabaseLayout)
database_notification_builder(&self.view_id, DatabaseNotification::DidUpdateDatabaseLayout)
.payload(payload)
.send();
@ -1214,7 +1214,7 @@ impl DatabaseViewEditor {
} => RowsChangePB::from_move(vec![deleted_row_id.into_inner()], vec![inserted_row.into()]),
};
send_notification(&self.view_id, DatabaseNotification::DidUpdateRow)
database_notification_builder(&self.view_id, DatabaseNotification::DidUpdateRow)
.payload(changeset)
.send();
}

View File

@ -22,7 +22,7 @@ impl FieldBuilder {
}
pub fn name(mut self, name: &str) -> Self {
self.field.name = name.to_owned();
name.clone_into(&mut self.field.name);
self
}

View File

@ -10,6 +10,7 @@ mod tests {
use crate::services::field::type_options::checkbox_type_option::*;
use crate::services::field::FieldBuilder;
use collab_database::fields::checkbox_type_option::CheckboxTypeOption;
use collab_database::template::util::ToCellString;
#[test]
fn checkout_box_description_test() {
@ -46,7 +47,7 @@ mod tests {
type_option
.decode_cell(&CheckboxCellDataPB::from_str(input_str).unwrap().into())
.unwrap()
.to_string(),
.to_cell_string(),
expected_str.to_owned()
);
}

View File

@ -4,7 +4,7 @@ use std::str::FromStr;
use collab_database::fields::checkbox_type_option::CheckboxTypeOption;
use collab_database::fields::Field;
use collab_database::rows::Cell;
use collab_database::template::util::ToCellString;
use flowy_error::FlowyResult;
use crate::entities::{CheckboxCellDataPB, CheckboxFilterPB, FieldType};
@ -48,7 +48,7 @@ impl CellDataDecoder for CheckboxTypeOption {
}
fn stringify_cell_data(&self, cell_data: <Self as TypeOption>::CellData) -> String {
cell_data.to_string()
cell_data.to_cell_string()
}
}

View File

@ -3,7 +3,7 @@ use std::str::FromStr;
use bytes::Bytes;
use collab::util::AnyMapExt;
use collab_database::rows::{new_cell_builder, Cell};
use collab_database::template::util::ToCellString;
use flowy_error::{FlowyError, FlowyResult};
use crate::entities::{CheckboxCellDataPB, FieldType};
@ -29,7 +29,7 @@ impl From<&Cell> for CheckboxCellDataPB {
impl From<CheckboxCellDataPB> for Cell {
fn from(data: CheckboxCellDataPB) -> Self {
let mut cell = new_cell_builder(FieldType::Checkbox);
cell.insert(CELL_DATA.into(), data.to_string().into());
cell.insert(CELL_DATA.into(), data.to_cell_string().into());
cell
}
}
@ -49,16 +49,6 @@ impl FromStr for CheckboxCellDataPB {
}
}
impl ToString for CheckboxCellDataPB {
fn to_string(&self) -> String {
if self.is_checked {
CHECK.to_string()
} else {
UNCHECK.to_string()
}
}
}
pub struct CheckboxCellDataParser();
impl CellProtobufBlobParser for CheckboxCellDataParser {
type Object = CheckboxCellDataPB;

View File

@ -1,8 +1,3 @@
use collab_database::fields::media_type_option::{MediaCellData, MediaTypeOption};
use collab_database::{fields::Field, rows::Cell};
use flowy_error::FlowyResult;
use std::cmp::Ordering;
use crate::{
entities::{FieldType, MediaCellChangeset, MediaCellDataPB, MediaFilterPB},
services::{
@ -14,6 +9,11 @@ use crate::{
sort::SortCondition,
},
};
use collab_database::fields::media_type_option::{MediaCellData, MediaTypeOption};
use collab_database::template::util::ToCellString;
use collab_database::{fields::Field, rows::Cell};
use flowy_error::FlowyResult;
use std::cmp::Ordering;
impl TypeOption for MediaTypeOption {
type CellData = MediaCellData;
@ -60,7 +60,7 @@ impl CellDataDecoder for MediaTypeOption {
}
fn stringify_cell_data(&self, cell_data: <Self as TypeOption>::CellData) -> String {
cell_data.to_string()
cell_data.to_cell_string()
}
}

View File

@ -4,6 +4,7 @@ use collab_database::fields::relation_type_option::RelationTypeOption;
use collab_database::rows::Cell;
use collab_database::template::relation_parse::RelationCellData;
use collab_database::template::util::ToCellString;
use flowy_error::FlowyResult;
use crate::entities::{RelationCellDataPB, RelationFilterPB};
@ -57,7 +58,7 @@ impl CellDataChangeset for RelationTypeOption {
impl CellDataDecoder for RelationTypeOption {
fn stringify_cell_data(&self, cell_data: RelationCellData) -> String {
cell_data.to_string()
cell_data.to_cell_string()
}
}

View File

@ -13,6 +13,7 @@ use collab_database::fields::TypeOptionData;
use collab_database::rows::Cell;
use flowy_error::FlowyResult;
use collab_database::template::util::ToCellString;
use std::cmp::Ordering;
impl TypeOption for MultiSelectTypeOption {
@ -80,7 +81,7 @@ impl CellDataChangeset for MultiSelectTypeOption {
select_ids.retain(|id| id != &delete_option_id);
}
tracing::trace!("Multi-select cell data: {}", select_ids.to_string());
tracing::trace!("Multi-select cell data: {}", select_ids.to_cell_string());
select_ids
},
};
@ -153,6 +154,7 @@ mod tests {
use collab_database::fields::select_type_option::{
MultiSelectTypeOption, SelectOption, SelectOptionIds, SelectTypeOption,
};
use collab_database::template::util::ToCellString;
#[test]
fn multi_select_insert_multi_option_test() {
@ -202,7 +204,7 @@ mod tests {
let changeset = SelectOptionCellChangeset::from_insert_option_id(&google.id);
let select_option_ids = multi_select.apply_changeset(changeset, None).unwrap().1;
assert_eq!(select_option_ids.to_string(), google.id);
assert_eq!(select_option_ids.to_cell_string(), google.id);
}
#[test]

View File

@ -13,6 +13,7 @@ use collab_database::fields::select_type_option::{
};
use collab_database::fields::{Field, TypeOptionData};
use collab_database::rows::Cell;
use collab_database::template::util::ToCellString;
use flowy_error::{internal_error, ErrorCode, FlowyResult};
use std::str::FromStr;
@ -118,7 +119,7 @@ where
Some(SelectOptionIds::from(transformed_ids))
},
FieldType::Checkbox => {
let cell_content = CheckboxCellDataPB::from(cell).to_string();
let cell_content = CheckboxCellDataPB::from(cell).to_cell_string();
let mut transformed_ids = Vec::new();
let options = self.options();
if let Some(option) = options.iter().find(|option| option.name == cell_content) {

View File

@ -4,7 +4,7 @@ use std::cmp::Ordering;
use collab_database::fields::text_type_option::RichTextTypeOption;
use collab_database::fields::Field;
use collab_database::rows::{new_cell_builder, Cell};
use collab_database::template::util::ToCellString;
use flowy_error::{FlowyError, FlowyResult};
use crate::entities::{FieldType, TextFilterPB};
@ -159,8 +159,8 @@ impl std::convert::From<String> for StringCellData {
}
}
impl ToString for StringCellData {
fn to_string(&self) -> String {
impl ToCellString for StringCellData {
fn to_cell_string(&self) -> String {
self.0.clone()
}
}

View File

@ -24,6 +24,7 @@ use collab_database::fields::translate_type_option::TranslateTypeOption;
use collab_database::fields::url_type_option::URLTypeOption;
use collab_database::fields::{TypeOptionCellReader, TypeOptionData};
use collab_database::rows::Cell;
use collab_database::template::util::ToCellString;
pub use collab_database::template::util::TypeOptionCellData;
use protobuf::ProtobufError;
use std::cmp::Ordering;
@ -42,7 +43,7 @@ pub trait TypeOption: From<TypeOptionData> + Into<TypeOptionData> + TypeOptionCe
///
type CellData: for<'a> From<&'a Cell>
+ TypeOptionCellData
+ ToString
+ ToCellString
+ Default
+ Send
+ Sync

View File

@ -1,5 +1,6 @@
use bytes::Bytes;
use protobuf::ProtobufError;
use std::fmt::Display;
#[derive(Default, Debug, Clone)]
pub struct ProtobufStr(pub String);
@ -23,9 +24,9 @@ impl std::convert::From<String> for ProtobufStr {
}
}
impl ToString for ProtobufStr {
fn to_string(&self) -> String {
self.0.clone()
impl Display for ProtobufStr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0.clone())
}
}

View File

@ -131,7 +131,7 @@ impl FilterController {
let task = Task::new(
&self.handler_id,
task_id,
TaskContent::Text(task_type.to_string()),
TaskContent::Text(task_type.to_json_string()),
qos,
);
self.task_scheduler.write().await.add_task(task);
@ -547,8 +547,8 @@ enum FilterEvent {
RowDidChanged(RowId),
}
impl ToString for FilterEvent {
fn to_string(&self) -> String {
impl FilterEvent {
fn to_json_string(&self) -> String {
serde_json::to_string(self).unwrap()
}
}

View File

@ -8,6 +8,7 @@ use collab::util::AnyMapExt;
use collab_database::database::gen_database_filter_id;
use collab_database::fields::select_type_option::SelectOptionIds;
use collab_database::rows::RowId;
use collab_database::template::util::ToCellString;
use collab_database::views::{FilterMap, FilterMapBuilder};
use flowy_error::{FlowyError, FlowyResult};
use lib_infra::box_any::BoxAny;
@ -365,12 +366,12 @@ impl<'a> From<&'a Filter> for FilterMap {
end: filter.end,
timestamp: filter.timestamp,
}
.to_string();
.to_json_string();
(filter.condition as u8, content)
},
FieldType::SingleSelect | FieldType::MultiSelect => {
let filter = condition_and_content.cloned::<SelectOptionFilterPB>()?;
let content = SelectOptionIds::from(filter.option_ids).to_string();
let content = SelectOptionIds::from(filter.option_ids).to_cell_string();
(filter.condition as u8, content)
},
FieldType::Checkbox => {

View File

@ -4,7 +4,7 @@ use collab_database::rows::{Cell, Cells, Row, RowId};
use flowy_error::FlowyResult;
use crate::entities::{GroupChangesPB, GroupPB, GroupRowsNotificationPB, InsertedGroupPB};
use crate::entities::{GroupPB, GroupRowsNotificationPB, InsertedGroupPB};
use crate::services::field::TypeOption;
use crate::services::group::{GroupChangeset, GroupData, MoveGroupRowContext};
@ -166,11 +166,6 @@ pub trait GroupController: Send + Sync {
/// * `context`: information about the row being moved and its destination
fn move_group_row(&mut self, context: MoveGroupRowContext) -> FlowyResult<DidMoveGroupRowResult>;
/// Updates the groups after a field change. (currently never does anything)
///
/// * `field`: new changeset
fn did_update_group_field(&mut self, field: &Field) -> FlowyResult<Option<GroupChangesPB>>;
/// Delete a group from the group configuration.
///
/// Return a list of deleted row ids and/or a new `TypeOptionData` if

View File

@ -10,7 +10,6 @@ use serde::Serialize;
use tracing::event;
use flowy_error::{FlowyError, FlowyResult};
use lib_dispatch::prelude::af_spawn;
use crate::entities::{GroupChangesPB, GroupPB, InsertedGroupPB};
use crate::services::field::RowSingleCellData;
@ -364,7 +363,7 @@ where
let configuration = (*self.setting).clone();
let delegate = self.delegate.clone();
let view_id = self.view_id.clone();
af_spawn(async move {
tokio::spawn(async move {
match delegate.save_configuration(&view_id, configuration).await {
Ok(_) => {},
Err(e) => {

View File

@ -10,8 +10,7 @@ use serde::Serialize;
use tracing::trace;
use crate::entities::{
FieldType, GroupChangesPB, GroupPB, GroupRowsNotificationPB, InsertedGroupPB, InsertedRowPB,
RowMetaPB,
FieldType, GroupPB, GroupRowsNotificationPB, InsertedGroupPB, InsertedRowPB, RowMetaPB,
};
use crate::services::cell::{get_cell_protobuf, CellProtobufBlobParser};
use crate::services::field::{default_type_option_data_from_type, TypeOption, TypeOptionCellData};
@ -382,10 +381,6 @@ where
Ok(result)
}
fn did_update_group_field(&mut self, _field: &Field) -> FlowyResult<Option<GroupChangesPB>> {
Ok(None)
}
async fn delete_group(
&mut self,
group_id: &str,

View File

@ -6,9 +6,7 @@ use collab_database::rows::{Cells, Row, RowId};
use flowy_error::FlowyResult;
use tracing::trace;
use crate::entities::{
GroupChangesPB, GroupPB, GroupRowsNotificationPB, InsertedGroupPB, InsertedRowPB,
};
use crate::entities::{GroupPB, GroupRowsNotificationPB, InsertedGroupPB, InsertedRowPB};
use crate::services::group::action::{
DidMoveGroupRowResult, DidUpdateGroupRowResult, GroupController,
};
@ -140,10 +138,6 @@ impl GroupController for DefaultGroupController {
})
}
fn did_update_group_field(&mut self, _field: &Field) -> FlowyResult<Option<GroupChangesPB>> {
Ok(None)
}
async fn delete_group(
&mut self,
_group_id: &str,

View File

@ -177,7 +177,7 @@ impl SortController {
let task = Task::new(
&self.handler_id,
task_id,
TaskContent::Text(task_type.to_string()),
TaskContent::Text(task_type.to_json_string()),
qos,
);
self.task_scheduler.write().await.add_task(task);
@ -351,8 +351,8 @@ enum SortEvent {
DeleteAllSorts,
}
impl ToString for SortEvent {
fn to_string(&self) -> String {
impl SortEvent {
fn to_json_string(&self) -> String {
serde_json::to_string(self).unwrap()
}
}

View File

@ -11,7 +11,6 @@ use flowy_database2::entities::{
DatabaseViewSettingPB, FieldType, FilterPB, FilterType, TextFilterConditionPB, TextFilterPB,
};
use flowy_database2::services::database_view::DatabaseViewChanged;
use lib_dispatch::prelude::af_spawn;
use crate::database::database_editor::DatabaseEditorTest;
@ -265,7 +264,7 @@ impl DatabaseFilterTest {
}
let change = change.unwrap();
let mut receiver = self.recv.take().unwrap();
af_spawn(async move {
tokio::spawn(async move {
match tokio::time::timeout(Duration::from_secs(2), receiver.recv()).await {
Ok(changed) => {
if let DatabaseViewChanged::FilterNotification(notification) = changed.unwrap() {

View File

@ -1,6 +1,7 @@
use crate::database::pre_fill_cell_test::script::DatabasePreFillRowCellTest;
use collab_database::fields::date_type_option::DateCellData;
use collab_database::fields::select_type_option::SELECTION_IDS_SEPARATOR;
use collab_database::template::util::ToCellString;
use flowy_database2::entities::{CreateRowPayloadPB, FieldType};
use std::collections::HashMap;
@ -182,7 +183,7 @@ async fn row_data_payload_with_invalid_date_time_test() {
timestamp: Some(1710510086),
..Default::default()
}
.to_string();
.to_cell_string();
test
.create_row_with_payload(CreateRowPayloadPB {

View File

@ -20,21 +20,4 @@ fn main() {
flowy_codegen::Project::TauriApp,
);
}
#[cfg(feature = "web_ts")]
{
flowy_codegen::ts_event::gen(
"document",
flowy_codegen::Project::Web {
relative_path: "../../".to_string(),
},
);
flowy_codegen::protobuf_file::ts_gen(
env!("CARGO_PKG_NAME"),
"document",
flowy_codegen::Project::Web {
relative_path: "../../".to_string(),
},
);
}
}

View File

@ -1,11 +1,10 @@
use crate::entities::{
DocEventPB, DocumentAwarenessStatesPB, DocumentSnapshotStatePB, DocumentSyncStatePB,
};
use crate::notification::{send_notification, DocumentNotification};
use crate::notification::{document_notification_builder, DocumentNotification};
use collab::preclude::Collab;
use collab_document::document::Document;
use futures::StreamExt;
use lib_dispatch::prelude::af_spawn;
pub fn subscribe_document_changed(doc_id: &str, document: &mut Document) {
let doc_id_clone_for_block_changed = doc_id.to_owned();
@ -14,7 +13,7 @@ pub fn subscribe_document_changed(doc_id: &str, document: &mut Document) {
tracing::trace!("subscribe_document_changed: {:?}", events);
// send notification to the client.
send_notification(
document_notification_builder(
&doc_id_clone_for_block_changed,
DocumentNotification::DidReceiveUpdate,
)
@ -26,7 +25,7 @@ pub fn subscribe_document_changed(doc_id: &str, document: &mut Document) {
document.subscribe_awareness_state("key", move |events| {
#[cfg(feature = "verbose_log")]
tracing::trace!("subscribe_awareness_state: {:?}", events);
send_notification(
document_notification_builder(
&doc_id_clone_for_awareness_state,
DocumentNotification::DidUpdateDocumentAwarenessState,
)
@ -38,11 +37,11 @@ pub fn subscribe_document_changed(doc_id: &str, document: &mut Document) {
pub fn subscribe_document_snapshot_state(collab: &Collab) {
let document_id = collab.object_id().to_string();
let mut snapshot_state = collab.subscribe_snapshot_state();
af_spawn(async move {
tokio::spawn(async move {
while let Some(snapshot_state) = snapshot_state.next().await {
if let Some(new_snapshot_id) = snapshot_state.snapshot_id() {
tracing::debug!("Did create document remote snapshot: {}", new_snapshot_id);
send_notification(
document_notification_builder(
&document_id,
DocumentNotification::DidUpdateDocumentSnapshotState,
)
@ -56,9 +55,9 @@ pub fn subscribe_document_snapshot_state(collab: &Collab) {
pub fn subscribe_document_sync_state(collab: &Collab) {
let document_id = collab.object_id().to_string();
let mut sync_state_stream = collab.subscribe_sync_state();
af_spawn(async move {
tokio::spawn(async move {
while let Some(sync_state) = sync_state_stream.next().await {
send_notification(
document_notification_builder(
&document_id,
DocumentNotification::DidUpdateDocumentSyncState,
)

View File

@ -29,7 +29,6 @@ use collab_integrate::collab_builder::{
use flowy_document_pub::cloud::DocumentCloudService;
use flowy_error::{internal_error, ErrorCode, FlowyError, FlowyResult};
use flowy_storage_pub::storage::{CreatedUpload, StorageService};
use lib_dispatch::prelude::af_spawn;
use crate::entities::UpdateDocumentAwarenessStatePB;
use crate::entities::{
@ -328,7 +327,7 @@ impl DocumentManager {
self.removing_documents.insert(doc_id, document);
let weak_removing_documents = Arc::downgrade(&self.removing_documents);
af_spawn(async move {
tokio::spawn(async move {
tokio::time::sleep(std::time::Duration::from_secs(120)).await;
if let Some(removing_documents) = weak_removing_documents.upgrade() {
if removing_documents.remove(&clone_doc_id).is_some() {

View File

@ -32,6 +32,9 @@ impl std::convert::From<i32> for DocumentNotification {
}
#[tracing::instrument(level = "trace")]
pub(crate) fn send_notification(id: &str, ty: DocumentNotification) -> NotificationBuilder {
pub(crate) fn document_notification_builder(
id: &str,
ty: DocumentNotification,
) -> NotificationBuilder {
NotificationBuilder::new(id, ty, DOCUMENT_OBSERVABLE_SOURCE)
}

View File

@ -112,7 +112,7 @@ async fn parse_readme_test() {
let data = json_str_to_hashmap(&block.data).ok();
assert!(data.is_some());
if let Some(data) = data {
assert!(data.get("delta").is_none());
assert!(!data.contains_key("delta"));
}
if let Some(external_id) = &block.external_id {

View File

@ -15,13 +15,4 @@ fn main() {
flowy_codegen::Project::TauriApp,
);
}
#[cfg(feature = "web_ts")]
flowy_codegen::protobuf_file::ts_gen(
env!("CARGO_PKG_NAME"),
"error",
flowy_codegen::Project::Web {
relative_path: "../../".to_string(),
},
);
}

View File

@ -1,321 +0,0 @@
use std::future::Future;
use crate::cloud::gen_view_id;
use collab_folder::{RepeatedViewIdentifier, View, ViewIcon, ViewIdentifier, ViewLayout};
use lib_infra::util::timestamp;
/// A builder for creating views, each able to have children views of
/// their own.
pub struct NestedViewBuilder {
pub uid: i64,
pub parent_view_id: String,
pub views: Vec<ParentChildViews>,
}
impl NestedViewBuilder {
pub fn new(parent_view_id: String, uid: i64) -> Self {
Self {
uid,
parent_view_id,
views: vec![],
}
}
pub async fn with_view_builder<F, O>(&mut self, view_builder: F)
where
F: Fn(ViewBuilder) -> O,
O: Future<Output = ParentChildViews>,
{
let builder = ViewBuilder::new(self.uid, self.parent_view_id.clone());
self.views.push(view_builder(builder).await);
}
pub fn build(&mut self) -> Vec<ParentChildViews> {
std::mem::take(&mut self.views)
}
}
/// A builder for creating a view.
/// The default layout of the view is [ViewLayout::Document]
pub struct ViewBuilder {
uid: i64,
parent_view_id: String,
view_id: String,
name: String,
desc: String,
layout: ViewLayout,
child_views: Vec<ParentChildViews>,
is_favorite: bool,
icon: Option<ViewIcon>,
}
impl ViewBuilder {
pub fn new(uid: i64, parent_view_id: String) -> Self {
Self {
uid,
parent_view_id,
view_id: gen_view_id().to_string(),
name: Default::default(),
desc: Default::default(),
layout: ViewLayout::Document,
child_views: vec![],
is_favorite: false,
icon: None,
}
}
pub fn view_id(&self) -> &str {
&self.view_id
}
pub fn with_view_id<T: ToString>(mut self, view_id: T) -> Self {
self.view_id = view_id.to_string();
self
}
pub fn with_layout(mut self, layout: ViewLayout) -> Self {
self.layout = layout;
self
}
pub fn with_name<T: ToString>(mut self, name: T) -> Self {
self.name = name.to_string();
self
}
pub fn with_desc(mut self, desc: &str) -> Self {
self.desc = desc.to_string();
self
}
pub fn with_icon(mut self, icon: &str) -> Self {
self.icon = Some(ViewIcon {
ty: collab_folder::IconType::Emoji,
value: icon.to_string(),
});
self
}
pub fn with_view(mut self, view: ParentChildViews) -> Self {
self.child_views.push(view);
self
}
pub fn with_child_views(mut self, mut views: Vec<ParentChildViews>) -> Self {
self.child_views.append(&mut views);
self
}
/// Create a child view for the current view.
/// The view created by this builder will be the next level view of the current view.
pub async fn with_child_view_builder<F, O>(mut self, child_view_builder: F) -> Self
where
F: Fn(ViewBuilder) -> O,
O: Future<Output = ParentChildViews>,
{
let builder = ViewBuilder::new(self.uid, self.view_id.clone());
self.child_views.push(child_view_builder(builder).await);
self
}
pub fn build(self) -> ParentChildViews {
let view = View {
id: self.view_id,
parent_view_id: self.parent_view_id,
name: self.name,
desc: self.desc,
created_at: timestamp(),
is_favorite: self.is_favorite,
layout: self.layout,
icon: self.icon,
created_by: Some(self.uid),
last_edited_time: 0,
children: RepeatedViewIdentifier::new(
self
.child_views
.iter()
.map(|v| ViewIdentifier {
id: v.parent_view.id.clone(),
})
.collect(),
),
last_edited_by: Some(self.uid),
extra: None,
};
ParentChildViews {
parent_view: view,
child_views: self.child_views,
}
}
}
#[derive(Clone)]
pub struct ParentChildViews {
pub parent_view: View,
pub child_views: Vec<ParentChildViews>,
}
impl ParentChildViews {
pub fn new(view: View) -> Self {
Self {
parent_view: view,
child_views: vec![],
}
}
pub fn flatten(self) -> Vec<View> {
FlattedViews::flatten_views(vec![self])
}
}
pub struct FlattedViews;
impl FlattedViews {
pub fn flatten_views(views: Vec<ParentChildViews>) -> Vec<View> {
let mut result = vec![];
for view in views {
result.push(view.parent_view);
result.append(&mut Self::flatten_views(view.child_views));
}
result
}
}
#[cfg(test)]
mod tests {
use crate::folder_builder::{FlattedViews, NestedViewBuilder};
#[tokio::test]
async fn create_first_level_views_test() {
let workspace_id = "w1".to_string();
let mut builder = NestedViewBuilder::new(workspace_id, 1);
builder
.with_view_builder(|view_builder| async { view_builder.with_name("1").build() })
.await;
builder
.with_view_builder(|view_builder| async { view_builder.with_name("2").build() })
.await;
builder
.with_view_builder(|view_builder| async { view_builder.with_name("3").build() })
.await;
let workspace_views = builder.build();
assert_eq!(workspace_views.len(), 3);
let views = FlattedViews::flatten_views(workspace_views);
assert_eq!(views.len(), 3);
}
#[tokio::test]
async fn create_view_with_child_views_test() {
let workspace_id = "w1".to_string();
let mut builder = NestedViewBuilder::new(workspace_id, 1);
builder
.with_view_builder(|view_builder| async {
view_builder
.with_name("1")
.with_child_view_builder(|child_view_builder| async {
child_view_builder.with_name("1_1").build()
})
.await
.with_child_view_builder(|child_view_builder| async {
child_view_builder.with_name("1_2").build()
})
.await
.build()
})
.await;
builder
.with_view_builder(|view_builder| async {
view_builder
.with_name("2")
.with_child_view_builder(|child_view_builder| async {
child_view_builder.with_name("2_1").build()
})
.await
.build()
})
.await;
let workspace_views = builder.build();
assert_eq!(workspace_views.len(), 2);
assert_eq!(workspace_views[0].parent_view.name, "1");
assert_eq!(workspace_views[0].child_views.len(), 2);
assert_eq!(workspace_views[0].child_views[0].parent_view.name, "1_1");
assert_eq!(workspace_views[0].child_views[1].parent_view.name, "1_2");
assert_eq!(workspace_views[1].child_views.len(), 1);
assert_eq!(workspace_views[1].child_views[0].parent_view.name, "2_1");
let views = FlattedViews::flatten_views(workspace_views);
assert_eq!(views.len(), 5);
}
#[tokio::test]
async fn create_three_level_view_test() {
let workspace_id = "w1".to_string();
let mut builder = NestedViewBuilder::new(workspace_id, 1);
builder
.with_view_builder(|view_builder| async {
view_builder
.with_name("1")
.with_child_view_builder(|child_view_builder| async {
child_view_builder
.with_name("1_1")
.with_child_view_builder(|b| async { b.with_name("1_1_1").build() })
.await
.with_child_view_builder(|b| async { b.with_name("1_1_2").build() })
.await
.build()
})
.await
.with_child_view_builder(|child_view_builder| async {
child_view_builder
.with_name("1_2")
.with_child_view_builder(|b| async { b.with_name("1_2_1").build() })
.await
.with_child_view_builder(|b| async { b.with_name("1_2_2").build() })
.await
.build()
})
.await
.build()
})
.await;
let workspace_views = builder.build();
assert_eq!(workspace_views.len(), 1);
assert_eq!(workspace_views[0].parent_view.name, "1");
assert_eq!(workspace_views[0].child_views.len(), 2);
assert_eq!(workspace_views[0].child_views[0].parent_view.name, "1_1");
assert_eq!(workspace_views[0].child_views[1].parent_view.name, "1_2");
assert_eq!(
workspace_views[0].child_views[0].child_views[0]
.parent_view
.name,
"1_1_1"
);
assert_eq!(
workspace_views[0].child_views[0].child_views[1]
.parent_view
.name,
"1_1_2"
);
assert_eq!(
workspace_views[0].child_views[1].child_views[0]
.parent_view
.name,
"1_2_1"
);
assert_eq!(
workspace_views[0].child_views[1].child_views[1]
.parent_view
.name,
"1_2_2"
);
let views = FlattedViews::flatten_views(workspace_views);
assert_eq!(views.len(), 7);
}
}

View File

@ -1,2 +1,3 @@
pub mod cloud;
pub mod entities;
pub mod query;

View File

@ -0,0 +1,6 @@
use lib_infra::async_trait::async_trait;
#[async_trait]
pub trait FolderQueryService: Send + Sync + 'static {
async fn get_sibling_ids(&self, parent_view_id: &str) -> Vec<String>;
}

View File

@ -21,8 +21,8 @@ arc-swap.workspace = true
unicode-segmentation = "1.10"
tracing.workspace = true
flowy-error = { path = "../flowy-error", features = [
"impl_from_dispatch_error",
"impl_from_collab_folder",
"impl_from_dispatch_error",
"impl_from_collab_folder",
] }
lib-dispatch = { workspace = true }
bytes.workspace = true
@ -42,6 +42,7 @@ async-trait.workspace = true
client-api = { workspace = true }
regex = "1.9.5"
futures = "0.3.30"
dashmap.workspace = true
[build-dependencies]

View File

@ -20,21 +20,4 @@ fn main() {
flowy_codegen::Project::TauriApp,
);
}
#[cfg(feature = "web_ts")]
{
flowy_codegen::ts_event::gen(
"folder",
flowy_codegen::Project::Web {
relative_path: "../../".to_string(),
},
);
flowy_codegen::protobuf_file::ts_gen(
env!("CARGO_PKG_NAME"),
"folder",
flowy_codegen::Project::Web {
relative_path: "../../".to_string(),
},
);
}
}

View File

@ -1,6 +1,6 @@
use crate::entities::parser::empty_str::NotEmptyStr;
use crate::entities::ViewLayoutPB;
use crate::share::{ImportParams, ImportType, ImportValue};
use crate::share::{ImportData, ImportItem, ImportParams, ImportType};
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
use flowy_error::FlowyError;
use lib_infra::validator_fn::required_not_empty_str;
@ -34,7 +34,7 @@ impl Default for ImportTypePB {
}
#[derive(Clone, Debug, ProtoBuf, Default)]
pub struct ImportValuePayloadPB {
pub struct ImportItemPayloadPB {
// the name of the import page
#[pb(index = 1)]
pub name: String,
@ -65,7 +65,7 @@ pub struct ImportPayloadPB {
pub parent_view_id: String,
#[pb(index = 2)]
pub values: Vec<ImportValuePayloadPB>,
pub items: Vec<ImportItemPayloadPB>,
}
impl TryInto<ImportParams> for ImportPayloadPB {
@ -76,38 +76,39 @@ impl TryInto<ImportParams> for ImportPayloadPB {
.map_err(|_| FlowyError::invalid_view_id())?
.0;
let mut values = Vec::new();
let items = self
.items
.into_iter()
.map(|item| {
let name = if item.name.is_empty() {
"Untitled".to_string()
} else {
item.name
};
for value in self.values {
let name = if value.name.is_empty() {
"Untitled".to_string()
} else {
value.name
};
let data = match (item.file_path, item.data) {
(Some(file_path), None) => ImportData::FilePath { file_path },
(None, Some(bytes)) => ImportData::Bytes { bytes },
(None, None) => {
return Err(FlowyError::invalid_data().with_context("The import data is empty"));
},
(Some(_), Some(_)) => {
return Err(FlowyError::invalid_data().with_context("The import data is ambiguous"));
},
};
let file_path = match value.file_path {
None => None,
Some(file_path) => Some(
NotEmptyStr::parse(file_path)
.map_err(|_| FlowyError::invalid_data().with_context("The import file path is empty"))?
.0,
),
};
let params = ImportValue {
name,
data: value.data,
file_path,
view_layout: value.view_layout.into(),
import_type: value.import_type.into(),
};
values.push(params);
}
Ok(ImportItem {
name,
data,
view_layout: item.view_layout.into(),
import_type: item.import_type.into(),
})
})
.collect::<Result<Vec<_>, _>>()?;
Ok(ImportParams {
parent_view_id,
values,
items,
})
}
}

View File

@ -11,8 +11,6 @@ pub mod view_operation;
mod manager_init;
mod manager_observer;
#[cfg(debug_assertions)]
pub mod manager_test_util;
pub mod publish_util;
pub mod share;

View File

@ -10,13 +10,13 @@ use crate::manager_observer::{
ChildViewChangeReason,
};
use crate::notification::{
send_current_workspace_notification, send_notification, FolderNotification,
folder_notification_builder, send_current_workspace_notification, FolderNotification,
};
use crate::publish_util::{generate_publish_name, view_pb_to_publish_view};
use crate::share::{ImportParams, ImportValue};
use crate::share::{ImportData, ImportItem, ImportParams};
use crate::util::{folder_not_init_error, workspace_data_not_sync_error};
use crate::view_operation::{
create_view, EncodedCollabWrapper, FolderOperationHandler, FolderOperationHandlers, ViewData,
create_view, EncodedCollabType, FolderOperationHandler, FolderOperationHandlers, ViewData,
};
use arc_swap::ArcSwapOption;
use client_api::entity::workspace_dto::PublishInfoView;
@ -57,11 +57,6 @@ pub trait FolderUser: Send + Sync {
}
pub struct FolderManager {
//FIXME: there's no sense in having a mutex_folder behind an RwLock. It's being obtained multiple
// times in the same function. FolderManager itself should be hidden behind RwLock if necessary.
// Unfortunately, this would require a changing the SyncPlugin architecture which requires access
// to Arc<RwLock<BorrowMut<Collab>>>. Eventually SyncPlugin should be refactored.
/// MutexFolder is the folder that is used to store the data.
pub(crate) mutex_folder: ArcSwapOption<RwLock<Folder>>,
pub(crate) collab_builder: Arc<AppFlowyCollabBuilder>,
pub(crate) user: Arc<dyn FolderUser>,
@ -75,7 +70,6 @@ impl FolderManager {
pub fn new(
user: Arc<dyn FolderUser>,
collab_builder: Arc<AppFlowyCollabBuilder>,
operation_handlers: FolderOperationHandlers,
cloud_service: Arc<dyn FolderCloudService>,
folder_indexer: Arc<dyn FolderIndexManager>,
store_preferences: Arc<KVStorePreferences>,
@ -84,7 +78,7 @@ impl FolderManager {
user,
mutex_folder: Default::default(),
collab_builder,
operation_handlers,
operation_handlers: Default::default(),
cloud_service,
folder_indexer,
store_preferences,
@ -93,10 +87,17 @@ impl FolderManager {
Ok(manager)
}
pub fn register_operation_handler(
&self,
layout: ViewLayout,
handler: Arc<dyn FolderOperationHandler + Send + Sync>,
) {
self.operation_handlers.insert(layout, handler);
}
#[instrument(level = "debug", skip(self), err)]
pub async fn get_current_workspace(&self) -> FlowyResult<WorkspacePB> {
let workspace_id = self.user.workspace_id()?;
match self.mutex_folder.load_full() {
None => {
let uid = self.user.user_id()?;
@ -118,6 +119,31 @@ impl FolderManager {
}
}
pub async fn get_folder_data(&self) -> FlowyResult<FolderData> {
let workspace_id = self.user.workspace_id()?;
let data = self
.mutex_folder
.load_full()
.ok_or_else(|| internal_error("The folder is not initialized"))?
.read()
.await
.get_folder_data(&workspace_id)
.ok_or_else(|| internal_error("Workspace id not match the id in current folder"))?;
Ok(data)
}
pub async fn get_encode_collab_from_disk(
&self,
view_id: &str,
layout: &ViewLayout,
) -> FlowyResult<EncodedCollabType> {
let handler = self.get_handler(layout)?;
let encoded_collab = handler
.get_encoded_collab_v1_from_disk(&self.user, view_id)
.await?;
Ok(encoded_collab)
}
/// Return a list of views of the current workspace.
/// Only the first level of child views are included.
pub async fn get_current_workspace_public_views(&self) -> FlowyResult<Vec<ViewPB>> {
@ -372,7 +398,7 @@ impl FolderManager {
.ok_or_else(|| FlowyError::internal().with_context("Cannot find the workspace ID"))?;
views.iter_mut().for_each(|view| {
view.view.parent_view_id = workspace_id.clone();
view.view.parent_view_id.clone_from(&workspace_id);
view.view.extra = Some(
serde_json::to_string(
&ViewExtraBuilder::new()
@ -460,7 +486,7 @@ impl FolderManager {
latest_view.space_info(),
);
views.iter_mut().for_each(|child_view| {
child_view.view.parent_view_id = latest_view.id.clone();
child_view.view.parent_view_id.clone_from(&latest_view.id);
});
},
}
@ -517,7 +543,13 @@ impl FolderManager {
);
if params.meta.is_empty() && params.initial_data.is_empty() {
handler
.create_view_with_default_data(user_id, &params.view_id, &params.name, view_layout.clone())
.create_default_view(
user_id,
&params.parent_view_id,
&params.view_id,
&params.name,
view_layout.clone(),
)
.await?;
} else {
encoded_collab = handler
@ -555,7 +587,13 @@ impl FolderManager {
let handler = self.get_handler(&view_layout)?;
let user_id = self.user.user_id()?;
handler
.create_view_with_default_data(user_id, &params.view_id, &params.name, view_layout.clone())
.create_default_view(
user_id,
&params.parent_view_id,
&params.view_id,
&params.name,
view_layout.clone(),
)
.await?;
let view = create_view(self.user.user_id()?, params, view_layout);
@ -707,7 +745,7 @@ impl FolderManager {
break;
}
ancestors.push(view_pb_without_child_views(view.as_ref().clone()));
parent_view_id = view.parent_view_id.clone();
parent_view_id.clone_from(&view.parent_view_id);
}
ancestors.reverse();
}
@ -740,7 +778,7 @@ impl FolderManager {
drop(folder);
// notify the parent view that the view is moved to trash
send_notification(view_id, FolderNotification::DidMoveViewToTrash)
folder_notification_builder(view_id, FolderNotification::DidMoveViewToTrash)
.payload(DeletedViewPB {
view_id: view_id.to_string(),
index: None,
@ -774,7 +812,7 @@ impl FolderManager {
.map(|v| v.id.clone())
.collect(),
);
send_notification("favorite", FolderNotification::DidUnfavoriteView)
folder_notification_builder("favorite", FolderNotification::DidUnfavoriteView)
.payload(RepeatedViewPB {
items: favorite_descendant_views,
})
@ -881,6 +919,20 @@ impl FolderManager {
}
}
pub async fn get_view(&self, view_id: &str) -> FlowyResult<Arc<View>> {
match self.mutex_folder.load_full() {
Some(folder) => {
let folder = folder.read().await;
Ok(
folder
.get_view(view_id)
.ok_or_else(FlowyError::record_not_found)?,
)
},
None => Err(FlowyError::internal().with_context("The folder is not initialized")),
}
}
/// Update the view with the given params.
#[tracing::instrument(level = "trace", skip(self), err)]
pub async fn update_view_with_params(&self, params: UpdateViewParams) -> FlowyResult<()> {
@ -1066,7 +1118,7 @@ impl FolderManager {
.await?;
if is_source_view {
new_view_id = duplicated_view.id.clone();
new_view_id.clone_from(&duplicated_view.id);
}
if sync_after_create {
@ -1248,7 +1300,9 @@ impl FolderManager {
.into_iter()
.map(|mut p| {
if let PublishPayload::Database(p) = &mut p {
p.data.visible_database_view_ids = selected_view_ids.clone();
p.data
.visible_database_view_ids
.clone_from(&selected_view_ids);
}
p
})
@ -1448,9 +1502,9 @@ impl FolderManager {
publish_name: Option<String>,
layout: ViewLayout,
) -> FlowyResult<PublishPayload> {
let handler: Arc<dyn FolderOperationHandler + Sync + Send> = self.get_handler(&layout)?;
let encoded_collab_wrapper: EncodedCollabWrapper = handler
.get_encoded_collab_v1_from_disk(self.user.clone(), view_id)
let handler = self.get_handler(&layout)?;
let encoded_collab_wrapper: EncodedCollabType = handler
.get_encoded_collab_v1_from_disk(&self.user, view_id)
.await?;
let view = self.get_view_pb(view_id).await?;
@ -1481,7 +1535,7 @@ impl FolderManager {
};
let payload = match encoded_collab_wrapper {
EncodedCollabWrapper::Database(v) => {
EncodedCollabType::Database(v) => {
let database_collab = v.database_encoded_collab.doc_state.to_vec();
let database_relations = v.database_relations;
let database_row_collabs = v
@ -1504,11 +1558,11 @@ impl FolderManager {
};
PublishPayload::Database(PublishDatabasePayload { meta, data })
},
EncodedCollabWrapper::Document(v) => {
EncodedCollabType::Document(v) => {
let data = v.document_encoded_collab.doc_state.to_vec();
PublishPayload::Document(PublishDocumentPayload { meta, data })
},
EncodedCollabWrapper::Unknown => PublishPayload::Unknown,
EncodedCollabType::Unknown => PublishPayload::Unknown,
};
Ok(payload)
@ -1522,13 +1576,13 @@ impl FolderManager {
} else {
FolderNotification::DidUnfavoriteView
};
send_notification("favorite", notification_type)
folder_notification_builder("favorite", notification_type)
.payload(RepeatedViewPB {
items: vec![view.clone()],
})
.send();
send_notification(&view.id, FolderNotification::DidUpdateView)
folder_notification_builder(&view.id, FolderNotification::DidUpdateView)
.payload(view)
.send()
}
@ -1536,7 +1590,7 @@ impl FolderManager {
async fn send_update_recent_views_notification(&self) {
let recent_views = self.get_my_recent_sections().await;
send_notification("recent_views", FolderNotification::DidUpdateRecentViews)
folder_notification_builder("recent_views", FolderNotification::DidUpdateRecentViews)
.payload(RepeatedViewIdPB {
items: recent_views.into_iter().map(|item| item.id).collect(),
})
@ -1566,7 +1620,7 @@ impl FolderManager {
if let Some(lock) = self.mutex_folder.load_full() {
let mut folder = lock.write().await;
folder.remove_all_my_trash_sections();
send_notification("trash", FolderNotification::DidUpdateTrash)
folder_notification_builder("trash", FolderNotification::DidUpdateTrash)
.payload(RepeatedTrashPB { items: vec![] })
.send();
}
@ -1592,7 +1646,7 @@ impl FolderManager {
for trash in deleted_trash {
let _ = self.delete_trash(&trash.id).await;
}
send_notification("trash", FolderNotification::DidUpdateTrash)
folder_notification_builder("trash", FolderNotification::DidUpdateTrash)
.payload(RepeatedTrashPB { items: vec![] })
.send();
}
@ -1622,42 +1676,36 @@ impl FolderManager {
}
/// Imports a single file to the folder and returns the encoded collab for immediate cloud sync.
#[allow(clippy::type_complexity)]
#[instrument(level = "debug", skip_all, err)]
pub(crate) async fn import_single_file(
&self,
parent_view_id: String,
import_data: ImportValue,
import_data: ImportItem,
) -> FlowyResult<(View, Vec<(String, CollabType, EncodedCollab)>)> {
// Ensure either data or file_path is provided
if import_data.data.is_none() && import_data.file_path.is_none() {
return Err(FlowyError::new(
ErrorCode::InvalidParams,
"Either data or file_path is required",
));
}
let handler = self.get_handler(&import_data.view_layout)?;
let view_id = gen_view_id().to_string();
let uid = self.user.user_id()?;
let mut encoded_collab = vec![];
// Import data from bytes if available
if let Some(data) = import_data.data {
encoded_collab = handler
.import_from_bytes(
uid,
&view_id,
&import_data.name,
import_data.import_type,
data,
)
.await?;
}
// Import data from file path if available
if let Some(file_path) = import_data.file_path {
handler
.import_from_file_path(&view_id, &import_data.name, file_path)
.await?;
info!("import single file from:{}", import_data.data);
match import_data.data {
ImportData::FilePath { file_path } => {
handler
.import_from_file_path(&view_id, &import_data.name, file_path)
.await?;
},
ImportData::Bytes { bytes } => {
encoded_collab = handler
.import_from_bytes(
uid,
&view_id,
&import_data.name,
import_data.import_type,
bytes,
)
.await?;
},
}
let params = CreateViewParams {
@ -1695,7 +1743,7 @@ impl FolderManager {
let workspace_id = self.user.workspace_id()?;
let mut objects = vec![];
let mut views = vec![];
for data in import_data.values {
for data in import_data.items {
// Import a single file and get the view and encoded collab data
let (view, encoded_collabs) = self
.import_single_file(import_data.parent_view_id.clone(), data)
@ -1751,7 +1799,7 @@ impl FolderManager {
}
if let Ok(view_pb) = self.get_view_pb(view_id).await {
send_notification(&view_pb.id, FolderNotification::DidUpdateView)
folder_notification_builder(&view_pb.id, FolderNotification::DidUpdateView)
.payload(view_pb)
.send();
@ -1765,10 +1813,7 @@ impl FolderManager {
}
/// Returns a handler that implements the [FolderOperationHandler] trait
fn get_handler(
&self,
view_layout: &ViewLayout,
) -> FlowyResult<Arc<dyn FolderOperationHandler + Send + Sync>> {
fn get_handler(&self, view_layout: &ViewLayout) -> FlowyResult<Arc<dyn FolderOperationHandler>> {
match self.operation_handlers.get(view_layout) {
None => Err(FlowyError::internal().with_context(format!(
"Get data processor failed. Unknown layout type: {:?}",

View File

@ -128,11 +128,6 @@ impl FolderManager {
folder_state_rx,
Arc::downgrade(&self.user),
);
subscribe_folder_snapshot_state_changed(
workspace_id.clone(),
weak_mutex_folder.clone(),
Arc::downgrade(&self.user),
);
subscribe_folder_trash_changed(
workspace_id.clone(),
section_change_rx,

View File

@ -1,16 +1,16 @@
use crate::entities::{
view_pb_with_child_views, view_pb_without_child_views, ChildViewUpdatePB, FolderSnapshotStatePB,
FolderSyncStatePB, RepeatedTrashPB, RepeatedViewPB, SectionViewsPB, ViewPB, ViewSectionPB,
view_pb_with_child_views, view_pb_without_child_views, ChildViewUpdatePB, FolderSyncStatePB,
RepeatedTrashPB, RepeatedViewPB, SectionViewsPB, ViewPB, ViewSectionPB,
};
use crate::manager::{get_workspace_private_view_pbs, get_workspace_public_view_pbs, FolderUser};
use crate::notification::{send_notification, FolderNotification};
use crate::notification::{folder_notification_builder, FolderNotification};
use collab::core::collab_state::SyncState;
use collab::lock::RwLock;
use collab_folder::{
Folder, SectionChange, SectionChangeReceiver, TrashSectionChange, View, ViewChange,
ViewChangeReceiver,
};
use lib_dispatch::prelude::af_spawn;
use std::collections::HashSet;
use std::sync::Weak;
use tokio_stream::wrappers::WatchStream;
@ -24,7 +24,7 @@ pub(crate) fn subscribe_folder_view_changed(
weak_mutex_folder: Weak<RwLock<Folder>>,
user: Weak<dyn FolderUser>,
) {
af_spawn(async move {
tokio::spawn(async move {
while let Ok(value) = rx.recv().await {
if let Some(user) = user.upgrade() {
if let Ok(actual_workspace_id) = user.workspace_id() {
@ -37,7 +37,7 @@ pub(crate) fn subscribe_folder_view_changed(
}
if let Some(lock) = weak_mutex_folder.upgrade() {
tracing::trace!("Did receive view change: {:?}", value);
trace!("Did receive view change: {:?}", value);
match value {
ViewChange::DidCreateView { view } => {
notify_child_views_changed(
@ -70,44 +70,12 @@ pub(crate) fn subscribe_folder_view_changed(
});
}
pub(crate) fn subscribe_folder_snapshot_state_changed(
workspace_id: String,
weak_mutex_folder: Weak<RwLock<Folder>>,
user: Weak<dyn FolderUser>,
) {
af_spawn(async move {
if let Some(folder) = weak_mutex_folder.upgrade() {
let mut state_stream = folder.read().await.subscribe_snapshot_state();
while let Some(snapshot_state) = state_stream.next().await {
if let Some(user) = user.upgrade() {
if let Ok(actual_workspace_id) = user.workspace_id() {
if actual_workspace_id != workspace_id {
// break the loop when the workspace id is not matched.
break;
}
}
}
if let Some(new_snapshot_id) = snapshot_state.snapshot_id() {
tracing::debug!("Did create folder remote snapshot: {}", new_snapshot_id);
send_notification(
&workspace_id,
FolderNotification::DidUpdateFolderSnapshotState,
)
.payload(FolderSnapshotStatePB { new_snapshot_id })
.send();
}
}
}
});
}
pub(crate) fn subscribe_folder_sync_state_changed(
workspace_id: String,
mut folder_sync_state_rx: WatchStream<SyncState>,
user: Weak<dyn FolderUser>,
) {
af_spawn(async move {
tokio::spawn(async move {
while let Some(state) = folder_sync_state_rx.next().await {
if let Some(user) = user.upgrade() {
if let Ok(actual_workspace_id) = user.workspace_id() {
@ -118,7 +86,7 @@ pub(crate) fn subscribe_folder_sync_state_changed(
}
}
send_notification(&workspace_id, FolderNotification::DidUpdateFolderSyncUpdate)
folder_notification_builder(&workspace_id, FolderNotification::DidUpdateFolderSyncUpdate)
.payload(FolderSyncStatePB::from(state))
.send();
}
@ -132,7 +100,7 @@ pub(crate) fn subscribe_folder_trash_changed(
weak_mutex_folder: Weak<RwLock<Folder>>,
user: Weak<dyn FolderUser>,
) {
af_spawn(async move {
tokio::spawn(async move {
while let Ok(value) = rx.recv().await {
if let Some(user) = user.upgrade() {
if let Ok(actual_workspace_id) = user.workspace_id() {
@ -160,7 +128,7 @@ pub(crate) fn subscribe_folder_trash_changed(
}
let repeated_trash: RepeatedTrashPB = folder.get_my_trash_info().into();
send_notification("trash", FolderNotification::DidUpdateTrash)
folder_notification_builder("trash", FolderNotification::DidUpdateTrash)
.payload(repeated_trash)
.send();
@ -204,7 +172,7 @@ pub(crate) fn notify_parent_view_did_change<T: AsRef<str>>(
// Post the notification
let parent_view_pb = view_pb_with_child_views(parent_view, child_views);
send_notification(parent_view_id, FolderNotification::DidUpdateView)
folder_notification_builder(parent_view_id, FolderNotification::DidUpdateView)
.payload(parent_view_pb)
.send();
}
@ -216,14 +184,14 @@ pub(crate) fn notify_parent_view_did_change<T: AsRef<str>>(
pub(crate) fn notify_did_update_section_views(workspace_id: &str, folder: &Folder) {
let public_views = get_workspace_public_view_pbs(workspace_id, folder);
let private_views = get_workspace_private_view_pbs(workspace_id, folder);
tracing::trace!(
trace!(
"Did update section views: public len = {}, private len = {}",
public_views.len(),
private_views.len()
);
// Notify the public views
send_notification(workspace_id, FolderNotification::DidUpdateSectionViews)
folder_notification_builder(workspace_id, FolderNotification::DidUpdateSectionViews)
.payload(SectionViewsPB {
section: ViewSectionPB::Public,
views: public_views,
@ -231,7 +199,7 @@ pub(crate) fn notify_did_update_section_views(workspace_id: &str, folder: &Folde
.send();
// Notify the private views
send_notification(workspace_id, FolderNotification::DidUpdateSectionViews)
folder_notification_builder(workspace_id, FolderNotification::DidUpdateSectionViews)
.payload(SectionViewsPB {
section: ViewSectionPB::Private,
views: private_views,
@ -241,7 +209,7 @@ pub(crate) fn notify_did_update_section_views(workspace_id: &str, folder: &Folde
pub(crate) fn notify_did_update_workspace(workspace_id: &str, folder: &Folder) {
let repeated_view: RepeatedViewPB = get_workspace_public_view_pbs(workspace_id, folder).into();
send_notification(workspace_id, FolderNotification::DidUpdateWorkspaceViews)
folder_notification_builder(workspace_id, FolderNotification::DidUpdateWorkspaceViews)
.payload(repeated_view)
.send();
}
@ -249,7 +217,7 @@ pub(crate) fn notify_did_update_workspace(workspace_id: &str, folder: &Folder) {
fn notify_view_did_change(view: View) -> Option<()> {
let view_id = view.id.clone();
let view_pb = view_pb_without_child_views(view);
send_notification(&view_id, FolderNotification::DidUpdateView)
folder_notification_builder(&view_id, FolderNotification::DidUpdateView)
.payload(view_pb)
.send();
None
@ -282,7 +250,7 @@ pub(crate) fn notify_child_views_changed(view_pb: ViewPB, reason: ChildViewChang
},
}
send_notification(&parent_view_id, FolderNotification::DidUpdateChildViews)
folder_notification_builder(&parent_view_id, FolderNotification::DidUpdateChildViews)
.payload(payload)
.send();
}

View File

@ -1,34 +0,0 @@
use crate::manager::{FolderManager, FolderUser};
use crate::view_operation::FolderOperationHandlers;
use collab::lock::RwLock;
use collab_folder::Folder;
use collab_integrate::collab_builder::AppFlowyCollabBuilder;
use flowy_folder_pub::cloud::FolderCloudService;
use flowy_search_pub::entities::FolderIndexManager;
use std::sync::Arc;
impl FolderManager {
pub fn get_mutex_folder(&self) -> Option<Arc<RwLock<Folder>>> {
self.mutex_folder.load_full()
}
pub fn get_cloud_service(&self) -> Arc<dyn FolderCloudService> {
self.cloud_service.clone()
}
pub fn get_user(&self) -> Arc<dyn FolderUser> {
self.user.clone()
}
pub fn get_indexer(&self) -> Arc<dyn FolderIndexManager> {
self.folder_indexer.clone()
}
pub fn get_collab_builder(&self) -> Arc<AppFlowyCollabBuilder> {
self.collab_builder.clone()
}
pub fn get_operation_handlers(&self) -> FolderOperationHandlers {
self.operation_handlers.clone()
}
}

View File

@ -69,7 +69,7 @@ impl std::convert::From<i32> for FolderNotification {
}
#[tracing::instrument(level = "trace")]
pub(crate) fn send_notification(id: &str, ty: FolderNotification) -> NotificationBuilder {
pub(crate) fn folder_notification_builder(id: &str, ty: FolderNotification) -> NotificationBuilder {
NotificationBuilder::new(id, ty, FOLDER_OBSERVABLE_SOURCE)
}
@ -77,7 +77,7 @@ pub(crate) fn send_notification(id: &str, ty: FolderNotification) -> Notificatio
/// user. Only one workspace can be opened at a time.
const CURRENT_WORKSPACE: &str = "current-workspace";
pub(crate) fn send_current_workspace_notification<T: ToBytes>(ty: FolderNotification, payload: T) {
send_notification(CURRENT_WORKSPACE, ty)
folder_notification_builder(CURRENT_WORKSPACE, ty)
.payload(payload)
.send();
}

View File

@ -1,4 +1,5 @@
use collab_folder::ViewLayout;
use std::fmt::{Display, Formatter};
#[derive(Clone, Debug)]
pub enum ImportType {
@ -10,16 +11,30 @@ pub enum ImportType {
}
#[derive(Clone, Debug)]
pub struct ImportValue {
pub struct ImportItem {
pub name: String,
pub data: Option<Vec<u8>>,
pub file_path: Option<String>,
pub data: ImportData,
pub view_layout: ViewLayout,
pub import_type: ImportType,
}
#[derive(Clone, Debug)]
pub enum ImportData {
FilePath { file_path: String },
Bytes { bytes: Vec<u8> },
}
impl Display for ImportData {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
ImportData::FilePath { file_path } => write!(f, "file: {}", file_path),
ImportData::Bytes { .. } => write!(f, "binary"),
}
}
}
#[derive(Clone, Debug)]
pub struct ImportParams {
pub parent_view_id: String,
pub values: Vec<ImportValue>,
pub items: Vec<ImportItem>,
}

View File

@ -7,7 +7,7 @@ use tokio::sync::RwLock;
use lib_infra::util::timestamp;
use crate::entities::{view_pb_with_child_views, ViewPB};
use crate::view_operation::FolderOperationHandlers;
use crate::view_operation::{FolderOperationHandler, FolderOperationHandlers};
pub struct DefaultFolderBuilder();
impl DefaultFolderBuilder {
@ -20,7 +20,19 @@ impl DefaultFolderBuilder {
workspace_id.clone(),
uid,
)));
for handler in handlers.values() {
// Collect all handlers from the DashMap into a vector.
//
// - `DashMap::iter()` returns references to the stored values, which are not `Send`
// and can cause issues in an `async` context where thread-safety is required.
// - By cloning the values into a `Vec`, we ensure they are owned and implement
// `Send + Sync`, making them safe to use in asynchronous operations.
// - This avoids lifetime conflicts and allows the handlers to be used in the
// asynchronous loop without tying their lifetimes to the DashMap.
//
let handler_clones: Vec<Arc<dyn FolderOperationHandler + Send + Sync>> =
handlers.iter().map(|entry| entry.value().clone()).collect();
for handler in handler_clones {
let _ = handler
.create_workspace_view(uid, workspace_view_builder.clone())
.await;

View File

@ -5,6 +5,7 @@ use collab_entity::CollabType;
use collab_folder::hierarchy_builder::NestedViewBuilder;
pub use collab_folder::View;
use collab_folder::ViewLayout;
use dashmap::DashMap;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
@ -18,7 +19,7 @@ use crate::manager::FolderUser;
use crate::share::ImportType;
#[derive(Debug, Clone)]
pub enum EncodedCollabWrapper {
pub enum EncodedCollabType {
Document(DocumentEncodedCollab),
Database(DatabaseEncodedCollab),
Unknown,
@ -43,7 +44,7 @@ pub type ImportedData = (String, CollabType, EncodedCollab);
/// view layout. Each [ViewLayout] will have a handler. So when creating a new
/// view, the [ViewLayout] will be used to get the handler.
#[async_trait]
pub trait FolderOperationHandler {
pub trait FolderOperationHandler: Send + Sync {
fn name(&self) -> &str;
/// Create the view for the workspace of new user.
/// Only called once when the user is created.
@ -70,9 +71,9 @@ pub trait FolderOperationHandler {
/// get the encoded collab data from the disk.
async fn get_encoded_collab_v1_from_disk(
&self,
_user: Arc<dyn FolderUser>,
_user: &Arc<dyn FolderUser>,
_view_id: &str,
) -> Result<EncodedCollabWrapper, FlowyError> {
) -> Result<EncodedCollabType, FlowyError> {
Err(FlowyError::not_support())
}
@ -103,9 +104,10 @@ pub trait FolderOperationHandler {
/// Create a view with the pre-defined data.
/// For example, the initial data of the grid/calendar/kanban board when
/// you create a new view.
async fn create_view_with_default_data(
async fn create_default_view(
&self,
user_id: i64,
parent_view_id: &str,
view_id: &str,
name: &str,
layout: ViewLayout,
@ -138,7 +140,7 @@ pub trait FolderOperationHandler {
}
pub type FolderOperationHandlers =
Arc<HashMap<ViewLayout, Arc<dyn FolderOperationHandler + Send + Sync>>>;
Arc<DashMap<ViewLayout, Arc<dyn FolderOperationHandler + Send + Sync>>>;
impl From<ViewLayoutPB> for ViewLayout {
fn from(pb: ViewLayoutPB) -> Self {

View File

@ -15,13 +15,4 @@ fn main() {
flowy_codegen::Project::TauriApp,
);
}
#[cfg(feature = "web_ts")]
flowy_codegen::protobuf_file::ts_gen(
env!("CARGO_PKG_NAME"),
"notification",
flowy_codegen::Project::Web {
relative_path: "../../".to_string(),
},
);
}

View File

@ -19,7 +19,7 @@ use collab_folder::{folder_diff::FolderViewChange, View, ViewIcon, ViewIndexCont
use flowy_error::{FlowyError, FlowyResult};
use flowy_search_pub::entities::{FolderIndexManager, IndexManager, IndexableData};
use flowy_user::services::authenticate_user::AuthenticateUser;
use lib_dispatch::prelude::af_spawn;
use strsim::levenshtein;
use tantivy::{
collector::TopDocs, directory::MmapDirectory, doc, query::QueryParser, schema::Field, Document,
@ -296,7 +296,7 @@ impl IndexManager for FolderIndexManagerImpl {
fn set_index_content_receiver(&self, mut rx: IndexContentReceiver, workspace_id: String) {
let indexer = self.clone();
let wid = workspace_id.clone();
af_spawn(async move {
tokio::spawn(async move {
while let Ok(msg) = rx.recv().await {
match msg {
IndexContent::Create(value) => match serde_json::from_value::<ViewIndexContent>(value) {

View File

@ -4,7 +4,7 @@ use std::sync::Arc;
use super::notifier::{SearchNotifier, SearchResultChanged, SearchResultReceiverRunner};
use crate::entities::{SearchFilterPB, SearchResultNotificationPB, SearchResultPB};
use flowy_error::FlowyResult;
use lib_dispatch::prelude::af_spawn;
use lib_infra::async_trait::async_trait;
use tokio::sync::broadcast;
@ -48,7 +48,7 @@ impl SearchManager {
// Initialize Search Notifier
let (notifier, _) = broadcast::channel(100);
af_spawn(SearchResultReceiverRunner(Some(notifier.subscribe())).run());
tokio::spawn(SearchResultReceiverRunner(Some(notifier.subscribe())).run());
Self { handlers, notifier }
}
@ -71,7 +71,7 @@ impl SearchManager {
let ch = channel.clone();
let notifier = self.notifier.clone();
af_spawn(async move {
tokio::spawn(async move {
let res = handler.perform_search(q.clone(), f).await;
let items = res.unwrap_or_default();

View File

@ -5,8 +5,8 @@ use client_api::entity::chat_dto::{
RepeatedChatMessage,
};
use flowy_ai_pub::cloud::{
ChatCloudService, ChatMessage, ChatMessageMetadata, ChatMessageType, LocalAIConfig, StreamAnswer,
StreamComplete, SubscriptionPlan,
ChatCloudService, ChatMessage, ChatMessageMetadata, ChatMessageType, ChatSettings, LocalAIConfig,
StreamAnswer, StreamComplete, SubscriptionPlan, UpdateChatParams,
};
use flowy_error::FlowyError;
use futures_util::{StreamExt, TryStreamExt};
@ -30,13 +30,14 @@ where
_uid: &i64,
workspace_id: &str,
chat_id: &str,
rag_ids: Vec<String>,
) -> Result<(), FlowyError> {
let chat_id = chat_id.to_string();
let try_get_client = self.inner.try_get_client();
let params = CreateChatParams {
chat_id,
name: "".to_string(),
rag_ids: vec![],
rag_ids,
};
try_get_client?
.create_chat(workspace_id, params)
@ -216,4 +217,31 @@ where
.await?;
Ok(plans)
}
async fn get_chat_settings(
&self,
workspace_id: &str,
chat_id: &str,
) -> Result<ChatSettings, FlowyError> {
let settings = self
.inner
.try_get_client()?
.get_chat_settings(workspace_id, chat_id)
.await?;
Ok(settings)
}
async fn update_chat_settings(
&self,
workspace_id: &str,
chat_id: &str,
params: UpdateChatParams,
) -> Result<(), FlowyError> {
self
.inner
.try_get_client()?
.update_chat_settings(workspace_id, chat_id, params)
.await?;
Ok(())
}
}

View File

@ -25,7 +25,7 @@ use flowy_server_pub::af_cloud_config::AFCloudConfiguration;
use flowy_storage_pub::cloud::StorageCloudService;
use flowy_user_pub::cloud::{UserCloudService, UserUpdate};
use flowy_user_pub::entities::UserTokenState;
use lib_dispatch::prelude::af_spawn;
use rand::Rng;
use semver::Version;
use tokio::select;
@ -132,7 +132,7 @@ impl AppFlowyServer for AppFlowyCloudServer {
let mut token_state_rx = self.client.subscribe_token_state();
let (watch_tx, watch_rx) = watch::channel(UserTokenState::Init);
let weak_client = Arc::downgrade(&self.client);
af_spawn(async move {
tokio::spawn(async move {
while let Ok(token_state) = token_state_rx.recv().await {
if let Some(client) = weak_client.upgrade() {
match token_state {
@ -172,7 +172,7 @@ impl AppFlowyServer for AppFlowyCloudServer {
};
let mut user_change = self.ws_client.subscribe_user_changed();
let (tx, rx) = tokio::sync::mpsc::channel(1);
af_spawn(async move {
tokio::spawn(async move {
while let Ok(user_message) = user_change.recv().await {
if let UserMessage::ProfileChange(change) = user_message {
let user_update = UserUpdate {
@ -302,7 +302,7 @@ fn spawn_ws_conn(
let cancellation_token = Arc::new(ArcSwap::new(Arc::new(CancellationToken::new())));
let cloned_cancellation_token = cancellation_token.clone();
af_spawn(async move {
tokio::spawn(async move {
if let Some(ws_client) = weak_ws_client.upgrade() {
let mut state_recv = ws_client.subscribe_connect_state();
while let Ok(state) = state_recv.recv().await {
@ -331,7 +331,7 @@ fn spawn_ws_conn(
});
let weak_ws_client = Arc::downgrade(ws_client);
af_spawn(async move {
tokio::spawn(async move {
while let Ok(token_state) = token_state_rx.recv().await {
info!("🟢token state: {:?}", token_state);
match token_state {

View File

@ -1,7 +1,7 @@
use client_api::entity::ai_dto::{CompletionType, LocalAIConfig, RepeatedRelatedQuestion};
use flowy_ai_pub::cloud::{
ChatCloudService, ChatMessage, ChatMessageMetadata, ChatMessageType, MessageCursor,
RepeatedChatMessage, StreamAnswer, StreamComplete, SubscriptionPlan,
ChatCloudService, ChatMessage, ChatMessageMetadata, ChatMessageType, ChatSettings, MessageCursor,
RepeatedChatMessage, StreamAnswer, StreamComplete, SubscriptionPlan, UpdateChatParams,
};
use flowy_error::FlowyError;
use lib_infra::async_trait::async_trait;
@ -18,6 +18,7 @@ impl ChatCloudService for DefaultChatCloudServiceImpl {
_uid: &i64,
_workspace_id: &str,
_chat_id: &str,
_rag_ids: Vec<String>,
) -> Result<(), FlowyError> {
Err(FlowyError::not_support().with_context("Chat is not supported in local server."))
}
@ -116,4 +117,21 @@ impl ChatCloudService for DefaultChatCloudServiceImpl {
.with_context("Get local ai config is not supported in local server."),
)
}
async fn get_chat_settings(
&self,
_workspace_id: &str,
_chat_id: &str,
) -> Result<ChatSettings, FlowyError> {
Err(FlowyError::not_support().with_context("Chat is not supported in local server."))
}
async fn update_chat_settings(
&self,
_workspace_id: &str,
_chat_id: &str,
_params: UpdateChatParams,
) -> Result<(), FlowyError> {
Err(FlowyError::not_support().with_context("Chat is not supported in local server."))
}
}

View File

@ -1,13 +1,9 @@
use std::fmt;
use anyhow::Error;
use bytes::Bytes;
use reqwest::{Response, StatusCode};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use flowy_error::{ErrorCode, FlowyError};
use lib_infra::future::{to_fut, Fut};
use flowy_error::ErrorCode;
#[derive(Debug, Serialize, Deserialize)]
pub struct HttpResponse {
@ -34,116 +30,3 @@ impl fmt::Display for HttpError {
write!(f, "{:?}: {}", self.code, self.msg)
}
}
/// Trait `ExtendedResponse` provides an extension method to handle and transform the response data.
///
/// This trait introduces a single method:
///
/// - `get_value`: It extracts the value from the response, and returns it as an instance of a type `T`.
/// This method will return an error if the status code of the response signifies a failure (not success).
/// Otherwise, it attempts to parse the response body into an instance of type `T`, which must implement
/// `serde::de::DeserializeOwned`, `Send`, `Sync`, and have a static lifetime ('static).
pub trait ExtendedResponse {
/// Returns the value of the response as a Future of `Result<T, Error>`.
///
/// If the status code of the response is not a success, returns an `Error`.
/// Otherwise, attempts to parse the response into an instance of type `T`.
///
/// # Type Parameters
///
/// * `T`: The type of the value to be returned. Must implement `serde::de::DeserializeOwned`,
/// `Send`, `Sync`, and have a static lifetime ('static).
fn get_value<T>(self) -> Fut<Result<T, Error>>
where
T: serde::de::DeserializeOwned + Send + Sync + 'static;
fn get_bytes(self) -> Fut<Result<Bytes, Error>>;
fn get_json(self) -> Fut<Result<Value, Error>>;
fn success(self) -> Fut<Result<(), Error>>;
fn success_with_body(self) -> Fut<Result<String, Error>>;
}
impl ExtendedResponse for Response {
fn get_value<T>(self) -> Fut<Result<T, Error>>
where
T: serde::de::DeserializeOwned + Send + Sync + 'static,
{
to_fut(async move {
let status_code = self.status();
if !status_code.is_success() {
return Err(parse_response_as_error(self).await.into());
}
let bytes = self.bytes().await?;
let value = serde_json::from_slice(&bytes).map_err(|e| {
FlowyError::new(
ErrorCode::Serde,
format!(
"failed to parse json: {}, body: {}",
e,
String::from_utf8_lossy(&bytes)
),
)
})?;
Ok(value)
})
}
fn get_bytes(self) -> Fut<Result<Bytes, Error>> {
to_fut(async move {
let status_code = self.status();
if !status_code.is_success() {
return Err(parse_response_as_error(self).await.into());
}
let bytes = self.bytes().await?;
Ok(bytes)
})
}
fn get_json(self) -> Fut<Result<Value, Error>> {
to_fut(async move {
if !self.status().is_success() {
return Err(parse_response_as_error(self).await.into());
}
let bytes = self.bytes().await?;
let value = serde_json::from_slice::<Value>(&bytes)?;
Ok(value)
})
}
fn success(self) -> Fut<Result<(), Error>> {
to_fut(async move {
if !self.status().is_success() {
return Err(parse_response_as_error(self).await.into());
}
Ok(())
})
}
fn success_with_body(self) -> Fut<Result<String, Error>> {
to_fut(async move {
if !self.status().is_success() {
return Err(parse_response_as_error(self).await.into());
}
Ok(self.text().await?)
})
}
}
async fn parse_response_as_error(response: Response) -> FlowyError {
let status_code = response.status();
let msg = response.text().await.unwrap_or_default();
if status_code == StatusCode::CONFLICT {
return FlowyError::new(ErrorCode::Conflict, msg);
}
FlowyError::new(
ErrorCode::HttpError,
format!(
"expected status code 2XX, but got {}, body: {}",
status_code, msg
),
)
}

View File

@ -4,7 +4,6 @@ use tokio::sync::oneshot::channel;
use flowy_database_pub::cloud::{CollabDocStateByOid, DatabaseCloudService, DatabaseSnapshot};
use lib_dispatch::prelude::af_spawn;
use lib_infra::async_trait::async_trait;
use lib_infra::future::FutureResult;
@ -37,7 +36,7 @@ where
let try_get_postgrest = self.server.try_get_weak_postgrest();
let object_id = object_id.to_string();
let (tx, rx) = channel();
af_spawn(async move {
tokio::spawn(async move {
tx.send(
async move {
let postgrest = try_get_postgrest?;
@ -60,7 +59,7 @@ where
) -> FutureResult<CollabDocStateByOid, Error> {
let try_get_postgrest = self.server.try_get_weak_postgrest();
let (tx, rx) = channel();
af_spawn(async move {
tokio::spawn(async move {
tx.send(
async move {
let postgrest = try_get_postgrest?;

View File

@ -8,7 +8,7 @@ use tokio::sync::oneshot::channel;
use flowy_document_pub::cloud::{DocumentCloudService, DocumentSnapshot};
use flowy_error::FlowyError;
use lib_dispatch::prelude::af_spawn;
use lib_infra::future::FutureResult;
use crate::supabase::api::request::{get_snapshots_from_server, FetchObjectUpdateAction};
@ -37,7 +37,7 @@ where
let try_get_postgrest = self.server.try_get_weak_postgrest();
let document_id = document_id.to_string();
let (tx, rx) = channel();
af_spawn(async move {
tokio::spawn(async move {
tx.send(
async move {
let postgrest = try_get_postgrest?;
@ -87,7 +87,7 @@ where
let try_get_postgrest = self.server.try_get_weak_postgrest();
let document_id = document_id.to_string();
let (tx, rx) = channel();
af_spawn(async move {
tokio::spawn(async move {
tx.send(
async move {
let postgrest = try_get_postgrest?;

View File

@ -14,7 +14,7 @@ use flowy_folder_pub::cloud::{
Workspace, WorkspaceRecord,
};
use flowy_folder_pub::entities::PublishPayload;
use lib_dispatch::prelude::af_spawn;
use lib_infra::future::FutureResult;
use lib_infra::util::timestamp;
@ -144,7 +144,7 @@ where
let try_get_postgrest = self.server.try_get_weak_postgrest();
let object_id = object_id.to_string();
let (tx, rx) = channel();
af_spawn(async move {
tokio::spawn(async move {
tx.send(
async move {
let postgrest = try_get_postgrest?;

View File

@ -21,7 +21,7 @@ use flowy_folder_pub::cloud::{Folder, FolderData, Workspace};
use flowy_user_pub::cloud::*;
use flowy_user_pub::entities::*;
use flowy_user_pub::DEFAULT_USER_NAME;
use lib_dispatch::prelude::af_spawn;
use lib_infra::box_any::BoxAny;
use lib_infra::future::FutureResult;
@ -271,7 +271,7 @@ where
let try_get_postgrest = self.server.try_get_weak_postgrest();
let (tx, rx) = channel();
let object_id = object_id.to_string();
af_spawn(async move {
tokio::spawn(async move {
tx.send(
async move {
let postgrest = try_get_postgrest?;
@ -313,7 +313,7 @@ where
let try_get_postgrest = self.server.try_get_weak_postgrest();
let (tx, rx) = channel();
let init_update = default_workspace_doc_state(&collab_object);
af_spawn(async move {
tokio::spawn(async move {
tx.send(
async move {
let postgrest = try_get_postgrest?
@ -351,7 +351,7 @@ where
let try_get_postgrest = self.server.try_get_weak_postgrest();
let cloned_collab_object = collab_object.clone();
let (tx, rx) = channel();
af_spawn(async move {
tokio::spawn(async move {
tx.send(
async move {
CreateCollabAction::new(cloned_collab_object, try_get_postgrest?, data)

View File

@ -51,6 +51,7 @@ pub trait PragmaExtension: ConnectionExtension {
self.query::<ST, T>(&query)
}
#[allow(dead_code)]
fn pragma_get<'query, ST, T>(&mut self, key: &str, schema: Option<&str>) -> Result<T>
where
SqlLiteral<ST>: LoadQuery<'query, SqliteConnection, T>,
@ -64,10 +65,12 @@ pub trait PragmaExtension: ConnectionExtension {
self.query::<ST, T>(&query)
}
#[allow(dead_code)]
fn pragma_set_busy_timeout(&mut self, timeout_ms: i32) -> Result<i32> {
self.pragma_ret::<Integer, i32, i32>("busy_timeout", timeout_ms, None)
}
#[allow(dead_code)]
fn pragma_get_busy_timeout(&mut self) -> Result<i32> {
self.pragma_get::<Integer, i32>("busy_timeout", None)
}
@ -80,12 +83,14 @@ pub trait PragmaExtension: ConnectionExtension {
self.pragma_ret::<Integer, i32, SQLiteJournalMode>("journal_mode", mode, schema)
}
#[allow(dead_code)]
fn pragma_get_journal_mode(&mut self, schema: Option<&str>) -> Result<SQLiteJournalMode> {
self
.pragma_get::<Text, String>("journal_mode", schema)?
.parse()
}
#[allow(dead_code)]
fn pragma_set_synchronous(
&mut self,
synchronous: SQLiteSynchronous,
@ -94,6 +99,7 @@ pub trait PragmaExtension: ConnectionExtension {
self.pragma("synchronous", synchronous as u8, schema)
}
#[allow(dead_code)]
fn pragma_get_synchronous(&mut self, schema: Option<&str>) -> Result<SQLiteSynchronous> {
self
.pragma_get::<Integer, i32>("synchronous", schema)?

View File

@ -107,7 +107,7 @@ pub async fn get_user_profile_handler(
let cloned_user_profile = user_profile.clone();
// Refresh the user profile in the background
af_spawn(async move {
tokio::spawn(async move {
if let Some(manager) = weak_manager.upgrade() {
let _ = manager.refresh_user_profile(&cloned_user_profile).await;
}
@ -274,7 +274,7 @@ pub async fn import_appflowy_data_folder_handler(
) -> Result<(), FlowyError> {
let data = data.try_into_inner()?;
let (tx, rx) = tokio::sync::oneshot::channel();
af_spawn(async move {
tokio::spawn(async move {
let result = async {
let manager = upgrade_manager(manager)?;
let imported_folder = prepare_import(

View File

@ -347,7 +347,10 @@ pub(crate) fn generate_import_data(
invalid_orphan_views
.iter_mut()
.for_each(|parent_child_views| {
parent_child_views.view.parent_view_id = other_view_id.clone();
parent_child_views
.view
.parent_view_id
.clone_from(&other_view_id);
});
let mut other_view = create_new_container_view(
current_session,
@ -364,7 +367,10 @@ pub(crate) fn generate_import_data(
views.push(other_view);
} else {
let first_view = views.first_mut().unwrap();
other_view.view.parent_view_id = first_view.view.id.clone();
other_view
.view
.parent_view_id
.clone_from(&first_view.view.id);
first_view.children.push(other_view);
}
}
@ -1250,7 +1256,7 @@ pub async fn upload_collab_objects_data(
// Spawn a new task to upload the collab objects data in the background. If the
// upload fails, we will retry the upload later.
// af_spawn(async move {
// tokio::spawn(async move {
if !objects.is_empty() {
batch_create(
uid,

View File

@ -15,7 +15,7 @@ use flowy_sqlite::{
DBConnection, Database, ExpressionMethods,
};
use flowy_user_pub::entities::{UserProfile, UserWorkspace};
use lib_dispatch::prelude::af_spawn;
use lib_infra::file_util::{unzip_and_replace, zip_folder};
use tracing::{error, event, info, instrument};
@ -60,7 +60,7 @@ impl UserDB {
if is_ok {
// If database is valid, update the shared map and initiate backup.
// Asynchronous backup operation.
af_spawn(async move {
tokio::spawn(async move {
if let Err(err) = tokio::task::spawn_blocking(move || zip_backup.backup()).await {
error!("Backup of collab db failed: {:?}", err);
}

View File

@ -23,7 +23,6 @@ use tokio::sync::Mutex;
use tokio_stream::StreamExt;
use tracing::{debug, error, event, info, instrument, warn};
use lib_dispatch::prelude::af_spawn;
use lib_infra::box_any::BoxAny;
use crate::entities::{AuthStateChangedPB, AuthStatePB, UserProfilePB, UserSettingPB};
@ -91,7 +90,7 @@ impl UserManager {
let weak_user_manager = Arc::downgrade(&user_manager);
if let Ok(user_service) = user_manager.cloud_services.get_user_service() {
if let Some(mut rx) = user_service.subscribe_user_update() {
af_spawn(async move {
tokio::spawn(async move {
while let Some(update) = rx.recv().await {
if let Some(user_manager) = weak_user_manager.upgrade() {
if let Err(err) = user_manager.handler_user_update(update).await {
@ -184,7 +183,7 @@ impl UserManager {
event!(tracing::Level::DEBUG, "Listen token state change");
let user_uid = user.uid;
let local_token = user.token.clone();
af_spawn(async move {
tokio::spawn(async move {
while let Some(token_state) = token_state_rx.next().await {
debug!("Token state changed: {:?}", token_state);
match token_state {
@ -678,7 +677,7 @@ impl UserManager {
params: UpdateUserProfileParams,
) -> Result<(), FlowyError> {
let server = self.cloud_services.get_user_service()?;
af_spawn(async move {
tokio::spawn(async move {
let credentials = UserCredentials::new(Some(token), Some(uid), None);
server.update_user(credentials, params).await
})

Some files were not shown because too many files have changed in this diff Show More