Flutter stream

Posted by koocyton on 2025-06-16
Estimated Reading Time 3 Minutes
Words 545 In Total
Viewed Times

AI 必用的 stream mcp 调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:get/get.dart';
import 'package:logger/logger.dart';
import 'package:onote/main.dart';
import 'package:onote/model/prompt_message.dart';
import 'package:onote/service/auth/auth_service.dart';
import 'package:onote/service/logger_factory.dart';
import 'package:onote/util/http_util.dart';

class AiCompletionsService {

static final Logger logger = LoggerFactory.instance;

static MainController get mainController => Get.find<MainController>();

static Stream<String> completions({required List<PromptMessage> messages, CancelToken? cancelToken}) async* {
if (messages.isEmpty || AuthService.currentSession==null) {
yield "[DONE]";
return;
}

// request
final stream = HttpUtil.streamStringPost(
mainController.mcpConfig.aiApi,
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ${AuthService.currentSession?.accessToken}'
},
data: {
'model': mainController.mcpConfig.aiModel,
'stream': true,
'messages': PromptMessage.toMapList(messages)
},
cancelToken: cancelToken
);

try {
await for (final response in stream) {
List<String> dataline = response.toString().split("\n");
for (String line in dataline) {
if (line.trim().startsWith("data:")) {
String lineJson = line.trim().substring(5).trim();
if (lineJson=="[DONE]") {
yield "[DONE]";
}
else if (lineJson.contains("assistant")) {
dynamic dyn = JsonDecoder().convert(lineJson);
if (dyn!=null && dyn["choices"]!=null && dyn["choices"].length>0 && dyn["choices"][0]["delta"]!=null && dyn["choices"][0]["delta"]["content"]!=null) {
yield dyn["choices"][0]["delta"]["content"];
}
}
}
}
}
}
catch(e) {
logger.i(e);
yield "[ERROR]";
}
}


static Stream<String> streamStringPost(String url, {CancelToken? cancelToken, Map<String, String>? headers, dynamic data}) async* {
try {
final response = await getDioInstance().post(url, data: data,
options: Options(
headers: buildHeaders(headers),
responseType: ResponseType.stream
),
cancelToken: cancelToken
);
if (response.data!=null) {
await for (final line in response.data.stream) {
yield utf8.decode(line);
}
}
}
catch(e) {
logger.i(e);
}
}
}

supabase Edge function proxy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import { serve } from "https://deno.land/std@0.177.0/http/server.ts"

serve(async (req) => {

const apiKey = Deno.env.get("OPENROUTER_API_KEY")

if (!apiKey) {
return new Response("Missing Openrouter API key", { status: 500 })
}

const targetURL = "https://openrouter.ai/api/v1/chat/completions"

const res = await fetch(targetURL, {
method: req.method,
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${apiKey}`,
},
body: req.body,
})

return new Response(res.body, {
status: res.status,
headers: {
// 重要:保留内容类型为 `text/event-stream`,否则前端无法处理
"Content-Type": res.headers.get("Content-Type") ?? "text/event-stream",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
},
})
})

如果您喜欢此博客或发现它对您有用,则欢迎对此发表评论。 也欢迎您共享此博客,以便更多人可以参与。 如果博客中使用的图像侵犯了您的版权,请与作者联系以将其删除。 谢谢 !