chore: implement ui

This commit is contained in:
Richard Shiue 2025-03-20 13:30:42 +08:00
parent 13a7ea07a8
commit 10f19069c6
4 changed files with 93 additions and 43 deletions

View File

@ -24,7 +24,8 @@ abstract class AIRepository {
List<AiWriterRecord> history = const [],
required CompletionTypePB completionType,
required Future<void> Function() onStart,
required Future<void> Function(String text) onProcess,
required Future<void> Function(String text) processMessage,
required Future<void> Function(String text) processAssistMessage,
required Future<void> Function() onEnd,
required void Function(AIError error) onError,
});
@ -40,18 +41,17 @@ class AppFlowyAIService implements AIRepository {
List<AiWriterRecord> history = const [],
required CompletionTypePB completionType,
required Future<void> Function() onStart,
required Future<void> Function(String text) onProcess,
required Future<void> Function(String text) processMessage,
required Future<void> Function(String text) processAssistMessage,
required Future<void> Function() onEnd,
required void Function(AIError error) onError,
}) async {
final stream = AppFlowyCompletionStream(
onStart: onStart,
onProcess: onProcess,
processMessage: processMessage,
processAssistMessage: processAssistMessage,
processError: onError,
onEnd: onEnd,
onError: onError,
onComment: (String text) async {
Log.info('Comment: $text');
},
);
final records = history.map((record) => record.toPB()).toList();
@ -82,26 +82,26 @@ class AppFlowyAIService implements AIRepository {
abstract class CompletionStream {
CompletionStream({
required this.onStart,
required this.onProcess,
required this.onComment,
required this.processMessage,
required this.processAssistMessage,
required this.processError,
required this.onEnd,
required this.onError,
});
final Future<void> Function() onStart;
final Future<void> Function(String text) onProcess;
final Future<void> Function(String text) onComment;
final Future<void> Function(String text) processMessage;
final Future<void> Function(String text) processAssistMessage;
final void Function(AIError error) processError;
final Future<void> Function() onEnd;
final void Function(AIError error) onError;
}
class AppFlowyCompletionStream extends CompletionStream {
AppFlowyCompletionStream({
required super.onStart,
required super.onProcess,
required super.onComment,
required super.processMessage,
required super.processAssistMessage,
required super.processError,
required super.onEnd,
required super.onError,
}) {
_startListening();
}
@ -116,7 +116,7 @@ class AppFlowyCompletionStream extends CompletionStream {
_subscription = _controller.stream.listen(
(event) async {
if (event == "AI_RESPONSE_LIMIT") {
onError(
processError(
AIError(
message: LocaleKeys.ai_textLimitReachedDescription.tr(),
code: AIErrorCode.aiResponseLimitExceeded,
@ -125,7 +125,7 @@ class AppFlowyCompletionStream extends CompletionStream {
}
if (event == "AI_IMAGE_RESPONSE_LIMIT") {
onError(
processError(
AIError(
message: LocaleKeys.ai_imageLimitReachedDescription.tr(),
code: AIErrorCode.aiImageResponseLimitExceeded,
@ -135,7 +135,7 @@ class AppFlowyCompletionStream extends CompletionStream {
if (event.startsWith("AI_MAX_REQUIRED:")) {
final msg = event.substring(16);
onError(
processError(
AIError(
message: msg,
code: AIErrorCode.other,
@ -148,11 +148,11 @@ class AppFlowyCompletionStream extends CompletionStream {
}
if (event.startsWith("data:")) {
await onProcess(event.substring(5));
await processMessage(event.substring(5));
}
if (event.startsWith("comment:")) {
await onComment(event.substring(8));
await processAssistMessage(event.substring(8));
}
if (event.startsWith("finish:")) {
@ -160,7 +160,7 @@ class AppFlowyCompletionStream extends CompletionStream {
}
if (event.startsWith("error:")) {
onError(
processError(
AIError(message: event.substring(6), code: AIErrorCode.other),
);
}

View File

@ -293,7 +293,7 @@ class AiWriterCubit extends Cubit<AiWriterState> {
AiWriterRecord.user(content: prompt),
);
},
onProcess: (text) async {
processMessage: (text) async {
await _textRobot.appendMarkdownText(
text,
updateSelection: false,
@ -301,14 +301,33 @@ class AiWriterCubit extends Cubit<AiWriterState> {
);
onAppendToDocument?.call();
},
processAssistMessage: (text) async {
if (state case final GeneratingAiWriterState generatingState) {
emit(
GeneratingAiWriterState(
command,
taskId: generatingState.taskId,
markdownText: generatingState.markdownText + text,
),
);
}
},
onEnd: () async {
await _textRobot.stop(
attributes: ApplySuggestionFormatType.replace.attributes,
);
emit(ReadyAiWriterState(command, isFirstRun: false));
records.add(
AiWriterRecord.ai(content: _textRobot.markdownText),
);
if (state case final GeneratingAiWriterState generatingState) {
await _textRobot.stop(
attributes: ApplySuggestionFormatType.replace.attributes,
);
emit(
ReadyAiWriterState(
command,
isFirstRun: false,
markdownText: generatingState.markdownText,
),
);
records.add(
AiWriterRecord.ai(content: _textRobot.markdownText),
);
}
},
onError: (error) async {
emit(ErrorAiWriterState(command, error: error));
@ -392,7 +411,7 @@ class AiWriterCubit extends Cubit<AiWriterState> {
);
_textRobot.start(position: position);
},
onProcess: (text) async {
processMessage: (text) async {
await _textRobot.appendMarkdownText(
text,
updateSelection: false,
@ -400,12 +419,29 @@ class AiWriterCubit extends Cubit<AiWriterState> {
);
onAppendToDocument?.call();
},
processAssistMessage: (text) async {
if (state case final GeneratingAiWriterState generatingState) {
emit(
GeneratingAiWriterState(
command,
taskId: generatingState.taskId,
markdownText: generatingState.markdownText + text,
),
);
}
},
onEnd: () async {
if (state case GeneratingAiWriterState _) {
if (state case final GeneratingAiWriterState generatingState) {
await _textRobot.stop(
attributes: ApplySuggestionFormatType.replace.attributes,
);
emit(ReadyAiWriterState(command, isFirstRun: false));
emit(
ReadyAiWriterState(
command,
isFirstRun: false,
markdownText: generatingState.markdownText,
),
);
}
records.add(
AiWriterRecord.ai(content: _textRobot.markdownText),
@ -468,7 +504,7 @@ class AiWriterCubit extends Cubit<AiWriterState> {
);
_textRobot.start(position: position);
},
onProcess: (text) async {
processMessage: (text) async {
await _textRobot.appendMarkdownText(
text,
updateSelection: false,
@ -476,8 +512,19 @@ class AiWriterCubit extends Cubit<AiWriterState> {
);
onAppendToDocument?.call();
},
processAssistMessage: (text) async {
if (state case final GeneratingAiWriterState generatingState) {
emit(
GeneratingAiWriterState(
command,
taskId: generatingState.taskId,
markdownText: generatingState.markdownText + text,
),
);
}
},
onEnd: () async {
if (state is GeneratingAiWriterState) {
if (state case final GeneratingAiWriterState generatingState) {
await _textRobot.stop(
attributes: ApplySuggestionFormatType.replace.attributes,
);
@ -485,6 +532,7 @@ class AiWriterCubit extends Cubit<AiWriterState> {
ReadyAiWriterState(
command,
isFirstRun: false,
markdownText: generatingState.markdownText,
),
);
records.add(
@ -524,7 +572,7 @@ class AiWriterCubit extends Cubit<AiWriterState> {
completionType: command.toCompletionType(),
history: records,
onStart: () async {},
onProcess: (text) async {
processMessage: (text) async {
if (state case final GeneratingAiWriterState generatingState) {
emit(
GeneratingAiWriterState(
@ -535,6 +583,7 @@ class AiWriterCubit extends Cubit<AiWriterState> {
);
}
},
processAssistMessage: (_) async {},
onEnd: () async {
if (state case final GeneratingAiWriterState generatingState) {
emit(

View File

@ -26,7 +26,7 @@ class _MockAIRepository extends Mock implements AppFlowyAIService {
List<AiWriterRecord> history = const [],
required CompletionTypePB completionType,
required Future<void> Function() onStart,
required Future<void> Function(String text) onProcess,
required Future<void> Function(String text) processMessage,
required Future<void> Function() onEnd,
required void Function(AIError error) onError,
}) async {
@ -37,7 +37,7 @@ class _MockAIRepository extends Mock implements AppFlowyAIService {
final lines = text.split('\n');
for (final line in lines) {
if (line.isNotEmpty) {
await onProcess('$_aiResponse $line\n\n');
await processMessage('$_aiResponse $line\n\n');
}
}
await onEnd();
@ -57,7 +57,7 @@ class _MockAIRepositoryLess extends Mock implements AppFlowyAIService {
List<AiWriterRecord> history = const [],
required CompletionTypePB completionType,
required Future<void> Function() onStart,
required Future<void> Function(String text) onProcess,
required Future<void> Function(String text) processMessage,
required Future<void> Function() onEnd,
required void Function(AIError error) onError,
}) async {
@ -66,7 +66,7 @@ class _MockAIRepositoryLess extends Mock implements AppFlowyAIService {
Future(() async {
await onStart();
// only return 1 line.
await onProcess('Hello World');
await processMessage('Hello World');
await onEnd();
}),
);
@ -84,7 +84,7 @@ class _MockAIRepositoryMore extends Mock implements AppFlowyAIService {
List<AiWriterRecord> history = const [],
required CompletionTypePB completionType,
required Future<void> Function() onStart,
required Future<void> Function(String text) onProcess,
required Future<void> Function(String text) processMessage,
required Future<void> Function() onEnd,
required void Function(AIError error) onError,
}) async {
@ -94,7 +94,7 @@ class _MockAIRepositoryMore extends Mock implements AppFlowyAIService {
await onStart();
// return 10 lines
for (var i = 0; i < 10; i++) {
await onProcess('Hello World\n\n');
await processMessage('Hello World\n\n');
}
await onEnd();
}),
@ -113,7 +113,7 @@ class _MockErrorRepository extends Mock implements AppFlowyAIService {
List<AiWriterRecord> history = const [],
required CompletionTypePB completionType,
required Future<void> Function() onStart,
required Future<void> Function(String text) onProcess,
required Future<void> Function(String text) processMessage,
required Future<void> Function() onEnd,
required void Function(AIError error) onError,
}) async {

View File

@ -197,6 +197,7 @@ where
.await
.map_err(FlowyError::from)?
.map_err(FlowyError::from);
Ok(stream.boxed())
}