mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2025-07-24 17:42:16 +00:00

* feat: start on AI plan+billing UI * chore: enable plan and billing * feat: cache workspace subscription + minor fixes (#5705) * feat: update api from billing * feat: add api for workspace subscription info (#5717) * feat: refactor and start integrating AI plans * feat: refine UI and add business logic for AI * feat: complete UIUX for AI and limits * chore: remove resolved todo * chore: localize remove addon dialog * chore: fix spacing issue for usage * fix: interpret subscription + usage on action * chore: update api for billing (#5735) * chore: update revisions * fix: remove subscription cache * fix: copy improvements + use consistent dialog * chore: update to the latest client api * feat: support updating billing period * Feat/ai billing cancel reason (#5752) * chore: add cancellation reason field * fix: ci add one retry for concurrent sign up * chore: merge with main * chore: half merge * chore: fix conflict * chore: observer error * chore: remove unneeded protobuf and remove unwrap * feat: added subscription plan details * chore: check error code and update sidebar toast * chore: periodically check billing state * chore: editor ai error * chore: return file upload error * chore: fmt * chore: clippy * chore: disable upload image when exceed storage limitation * chore: remove todo * chore: remove openai i18n * chore: update log * chore: update client-api to fix stream error * chore: clippy * chore: fix language file * chore: disable billing UI --------- Co-authored-by: Zack Fu Zi Xiang <speed2exe@live.com.sg> Co-authored-by: nathan <nathan@appflowy.io>
135 lines
4.0 KiB
Dart
135 lines
4.0 KiB
Dart
import 'dart:async';
|
|
import 'dart:ffi';
|
|
import 'dart:isolate';
|
|
|
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/ai_client.dart';
|
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/error.dart';
|
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/text_completion.dart';
|
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/smart_edit_action.dart';
|
|
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
|
import 'package:appflowy_backend/protobuf/flowy-chat/entities.pb.dart';
|
|
import 'package:appflowy_result/appflowy_result.dart';
|
|
import 'package:easy_localization/easy_localization.dart';
|
|
import 'package:fixnum/fixnum.dart' as fixnum;
|
|
|
|
class AppFlowyAIService implements AIRepository {
|
|
@override
|
|
Future<FlowyResult<List<String>, AIError>> generateImage({
|
|
required String prompt,
|
|
int n = 1,
|
|
}) {
|
|
throw UnimplementedError();
|
|
}
|
|
|
|
@override
|
|
Future<void> getStreamedCompletions({
|
|
required String prompt,
|
|
required Future<void> Function() onStart,
|
|
required Future<void> Function(TextCompletionResponse response) onProcess,
|
|
required Future<void> Function() onEnd,
|
|
required void Function(AIError error) onError,
|
|
String? suffix,
|
|
int maxTokens = 2048,
|
|
double temperature = 0.3,
|
|
bool useAction = false,
|
|
}) {
|
|
throw UnimplementedError();
|
|
}
|
|
|
|
@override
|
|
Future<CompletionStream> streamCompletion({
|
|
required String text,
|
|
required CompletionTypePB completionType,
|
|
required Future<void> Function() onStart,
|
|
required Future<void> Function(String text) onProcess,
|
|
required Future<void> Function() onEnd,
|
|
required void Function(AIError error) onError,
|
|
}) async {
|
|
final stream = CompletionStream(
|
|
onStart,
|
|
onProcess,
|
|
onEnd,
|
|
onError,
|
|
);
|
|
final payload = CompleteTextPB(
|
|
text: text,
|
|
completionType: completionType,
|
|
streamPort: fixnum.Int64(stream.nativePort),
|
|
);
|
|
|
|
// ignore: unawaited_futures
|
|
ChatEventCompleteText(payload).send();
|
|
return stream;
|
|
}
|
|
}
|
|
|
|
CompletionTypePB completionTypeFromInt(SmartEditAction action) {
|
|
switch (action) {
|
|
case SmartEditAction.summarize:
|
|
return CompletionTypePB.MakeShorter;
|
|
case SmartEditAction.fixSpelling:
|
|
return CompletionTypePB.SpellingAndGrammar;
|
|
case SmartEditAction.improveWriting:
|
|
return CompletionTypePB.ImproveWriting;
|
|
case SmartEditAction.makeItLonger:
|
|
return CompletionTypePB.MakeLonger;
|
|
}
|
|
}
|
|
|
|
class CompletionStream {
|
|
CompletionStream(
|
|
Future<void> Function() onStart,
|
|
Future<void> Function(String text) onProcess,
|
|
Future<void> Function() onEnd,
|
|
void Function(AIError error) onError,
|
|
) {
|
|
_port.handler = _controller.add;
|
|
_subscription = _controller.stream.listen(
|
|
(event) async {
|
|
if (event == "AI_RESPONSE_LIMIT") {
|
|
onError(
|
|
AIError(
|
|
message: LocaleKeys.sideBar_aiResponseLitmit.tr(),
|
|
code: AIErrorCode.aiResponseLimitExceeded,
|
|
),
|
|
);
|
|
}
|
|
|
|
if (event.startsWith("start:")) {
|
|
await onStart();
|
|
}
|
|
|
|
if (event.startsWith("data:")) {
|
|
await onProcess(event.substring(5));
|
|
}
|
|
|
|
if (event.startsWith("finish:")) {
|
|
await onEnd();
|
|
}
|
|
|
|
if (event.startsWith("error:")) {
|
|
onError(AIError(message: event.substring(6)));
|
|
}
|
|
},
|
|
);
|
|
}
|
|
|
|
final RawReceivePort _port = RawReceivePort();
|
|
final StreamController<String> _controller = StreamController.broadcast();
|
|
late StreamSubscription<String> _subscription;
|
|
int get nativePort => _port.sendPort.nativePort;
|
|
|
|
Future<void> dispose() async {
|
|
await _controller.close();
|
|
await _subscription.cancel();
|
|
_port.close();
|
|
}
|
|
|
|
StreamSubscription<String> listen(
|
|
void Function(String event)? onData,
|
|
) {
|
|
return _controller.stream.listen(onData);
|
|
}
|
|
}
|