Compose Multiplatform+Kotlin Multiplatfrom 第四弹跨平台

文章目录

    • 引言
    • 功能效果
    • 开发准备
    • 依赖使用
      • gradle依赖库
      • MVI+Flow设计
      • 富文本显示
    • 总结

引言

  Compose Multiplatform+kotlin Multiplatfrom 今天已经到compose v1.7.3,从界面UI框架上实战开发看,很多api都去掉实验性注解,表示稳定使用了!然后继续这套框架做技术预研,上马目前所有系统,即Android、iOS、macOs、Windows、Linux(没有系统可验证)、Browser。
  现在AI大模型是前沿技术广泛引用的排头兵,这次引入deepseek的多轮会话v3大模型,使用调用api的方式完成数据的显示。
  *** 基于保密不会将重要的算法、工厂引擎代码透露。

功能效果

  目前成功在Android真机,Macbook pro2022,Windows10,iOS 17模拟器运行,本地构建要依赖的库实在是太多了,稍微一个错编译就是很漫长,还有相当多技术在预研,后面后把完整代码公布出来。

运行效果图gif预览,点进去看不到要登录下gitee
gif图太大了,没法压缩到10mb,给出链接iOS/macOs
在这里插入图片描述

Windows版电脑有水印不好截图,放下载包.exe

运行截图1 截图2 截图3

开发准备

  • 依托Android的compose框架为界面开发,Android studio最新版Android Studio Koala | 2024.1.1 Patch 1 Build #AI-241.18034.62.2411.12071903, built on July 11, 2024,本地的JDK为17或21,我把第三方库comShot引进来就要21,
    XCode是编译iOS的,现在只运行过在模拟器,所有系统配置好环境变量。
    开发电脑两台Windows 10专业版,MacBook2022,两边都可自主同步编译,主要问题是gradle.properties文件中指定了项目的JDK路径,
    两台电脑是不一致的,必须清楚自己的JDK版本和路径,$java -version 。
  • 去deepseek获取免费一个月的api key,其实换openAI的key也可以,但是注意api的路径和域名,确定当前compose plugin最新版本,目前我的所有依赖都是最新版本,已经踏平了很多坑。
  • 明确需求,部分实现:
  • 1.多轮机器对话 ,支持多个大模型切换
  • 2.本地会话记录,sqlDelight数据库
  • 3.黑暗模式切换,支持所有系统,支持代码框包裹
  • 4.富文本对代码支持,对公式函数支持
  • 5.系统图册选择后模型问答
  • 6.截图后的模型问答,desktop端自由裁剪很难,移动端还行
  • 7.多端编译,一套代码开发,适配不同机型和系统,磨平差异性
  • 8.desktop端可随处弹出一个小功能窗快捷询问,移动端则可以是语音和浮窗长驻
  • 9.系统粘贴版监听,本地存储粘贴记录
  • 10.本地知识库构建,离线小模型处理,多场景搜索算法智能调用
  • 10.MVI+Flow流+ViewModel+Koin3开发架构设计,我并没有引入第三方的框架来做,我去研读了下FlowMVI的设计,它引入了太多其他库,而且把逻辑块的设计完全剥离,感觉自己没完全理解不敢用,最终目的就是异步的流式编程,在声明式开发中用户意图单向流动到界面,数据源唯一可信,界面就能按我们的意图渲染,再结合副作用effect,觉得比原生好用好多,就是现在不太会画canvas
  • 完全自动运行,类似手机AI应用帮我们点饭,解决隐私权限,模拟点击,持续日志信息队列分析

依赖使用

  在multiplatform compose 开发建议还是多依赖第三方的团队sdk,实在没有的再搞接口expect去实现,因为多端差异性开发工作量实在是大,整个UI框架现在迭代速度也挺快的,运行的效果差异巨大,不同系统差异,当同系统下还有机型差异,系统版本插件,就键盘弹起的坑、状态栏样式等都不及预期。

  • 通用类commonMain

  • openai-client,这个库用kotlin把整个大模型请求都包装好了,我在java时用okhttp3是成功实现SSE数据流的结果,但在kotlin multiplatform时每次接口数据都是等一会然后所有data包一次性返回来,我对比过openai-client的请求源码分析也没太大逻辑差别,暂时解决不了原生kotlin用ktor3请求SSE效果,有实现可以告知>-<

  • ktor3.0 + kotlinx-json ,这是网络请求库和序列化,在大模型的请求参数中,每个实体类的值都会转为Json参数,然后post/json的方式获取data 包,正常的SSE返回应该每个包都是data :{},里面的chioe[]又可能为空,所以记得善用序列化的注解,不同的大模型对参数要求有小差异,不出现也要声明把值至为null,不然没有时偶尔报错导致程序解析中断。

  • multiplatform-settings,本地数据持久化,之前的文章也提过,现在理解透彻后重新实现了依赖注入的单例,改为suspend的协程异步执行,虽然优化了线程,当异步时小心业务使用的逻辑错误,取值时不在同一个线程。

  • napier,日志库,Android的日志Logcat,iOS在console,Desktop在terminal,都要初始化后才能使用。

  • precompose,路由页面跳转库,这个自带viewModel,内部也使用koin注入,所以每个composable页面使用的viewModel都继承precompose库下的viewModel,我里面封装了NavigateRoute整个路由表。

  • sqlDelight,数据库,会话记录历史存放工具,要自己写sql语句

  • comShot,Android和windows的composalbe组件截图,就是对UI控件的截图,我尝试整个屏幕构建一个透明的composable组件然后触发截图,但是在自由拖拽矩形时没法跳出应用外的界面,后面再研究下。

  • vinceglb/FileKit,文件选择器,以前不知道有这库本地文件都是用okio expect保存的

  • coil3,图片异步加载,gif/video只支持Android;可选sketch库

  • webview,io.github.kevinnzou:compose-webview-multiplatform修复很及时,注意和路由库使用的bug,用voyager和它不兼容

  • 系统特性类

  • 富文本显示,https://github.com/halilozercan/compose-richtext,只支持Android和Desktop,而且调试bug不多,

  • 富文本编辑或显示,https://github.com/MohamedRejeb/compose-rich-editor,全平台,但是挺多问题的,iOS不同写法会内容闪烁,只支持行内代码‘’,不支持块代码包裹如‘’‘ java’‘’。

  • 权限库,dev.icerock.moko:permissions,其他可选真不多。

gradle依赖库

[versions]
agp = "8.5.0"
#ksp = "2.0.20-1.0.24"
kotlin = "2.1.0"
ksp = "2.1.0-1.0.29"
compose-compiler = "1.5.4"
java = "21"
compose = "1.7.6"
kotlinxDatetime = "0.6.0"
compose-material3 = "1.3.1"
activityCompose = "1.9.2"
compose-plugin = "1.7.3"
androidAppCompatVersion = "1.7.0"
sqlDelight = "2.0.2"
buildkonfigGradlePlugin = "0.15.1"
koin = "4.0.0"
mokoMvvmVersion = "0.16.1"
androidLifecycleVersion = "2.2.0"
stately = "2.0.6"
ktor = "3.1.0"
uuid = "0.8.4"
settings = "1.3.0"
mokopermission = "0.18.1"
perference = "1.2.0"
permissionX = "1.8.0"
coil3 = "3.1.0"
windowSize = "0.5.0"
sheet = "0.1.2"
pagingCommonVersion = "3.3.0-alpha02-0.5.1"
#precompose = "1.6.2"
precompose = "1.7.0-alpha01" #预发布
okio = "3.9.0"
bugly = "4.1.9"
file = "0.8.8"
coroutines-core = "1.10.1"
coreSplashscreen = "1.0.1"
datastorePreferences = "1.1.1"
lifecycleRuntimeKtx = "2.7.0"
#coreKtx = "1.10.1"
coreKtx="1.15.0"
jna = "5.15.0"
toast4j = "0.2.0"
kotlinxCoroutinesSwing = "1.10.1"
kotlinxSerializationJson = "1.7.3"
desugar_jdk_libs = "2.1.3"
accompanist-systemUIController = "0.36.0"
androidx-lifecycle = "2.8.4"
androidx-navigation = "2.8.0-alpha10"
napier = "2.7.1"
richtext = "1.0.0-alpha02"
richedit = "1.0.0-rc11"
reveal = "3.2.0"
capture = "0.3.0"
ktoken = "0.3.0"
openai-client = "4.0.1"
webview="1.9.40"[libraries]
#UI框架相关
buildkonfig-gradle-plugin = { module = "com.codingfeline.buildkonfig:buildkonfig-gradle-plugin", version.ref = "buildkonfigGradlePlugin" }
core-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "coreSplashscreen" }
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activityCompose" }
androidx-core = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines-core" }
kotlinx-coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "kotlinxCoroutinesSwing" }
kotlinX-serializationJson = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kotlinxSerializationJson" }uuid = { module = "com.benasher44:uuid", version.ref = "uuid" }
windowSize = { module = "dev.chrisbanes.material3:material3-window-size-class-multiplatform", version.ref = "windowSize" }
bottomSheet = { module = "com.github.skydoves:flexible-bottomsheet-material3", version.ref = "sheet" }
paging-compose = { module = "app.cash.paging:paging-compose-common", version.ref = "pagingCommonVersion" }
kotlin-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinxDatetime" }
toast4j = { module = "de.mobanisto:toast4j", version.ref = "toast4j" }
jna = { module = "net.java.dev.jna:jna", version.ref = "jna" }
desugar_jdk_libs = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugar_jdk_libs" }
androidx-lifecycle-viewmodel-compose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "androidx-lifecycle" }
androidx-navigation-compose = { module = "org.jetbrains.androidx.navigation:navigation-compose", version.ref = "androidx-navigation" }
accompanist-systemUIController = { module = "com.google.accompanist:accompanist-systemuicontroller", version.ref = "accompanist-systemUIController" }
napier = { module = "io.github.aakira:napier", version.ref = "napier" }
stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
compose-webviews = { module = "io.github.kevinnzou:compose-webview-multiplatform", version.ref = "webview" }#没有iOS的富文本处理
richtext-core = { module = "com.halilibo.compose-richtext:richtext-ui", version.ref = "richtext" }
richtext-mark = { module = "com.halilibo.compose-richtext:richtext-commonmark", version.ref = "richtext" }
richtext-markdown = { module = "com.halilibo.compose-richtext:richtext-markdown", version.ref = "richtext" }
richtext-material = { module = "com.halilibo.compose-richtext:richtext-ui-material", version.ref = "richtext" }
richtext-material3 = { module = "com.halilibo.compose-richtext:richtext-ui-material3", version.ref = "richtext" }
rich-editor = { module = "com.mohamedrejeb.richeditor:richeditor-compose", version.ref = "richedit" }reveal = { module = "com.svenjacobs.reveal:reveal-core", version.ref = "reveal" }
#capture-shot = { module = "ir.mahozad.multiplatform:comshot", version.ref = "capture" }#Android特有
androidx-perference = { module = "androidx.preference:preference-ktx", version.ref = "perference" }
androidx-lifecycle = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
appCompat = { module = "androidx.appcompat:appcompat", version.ref = "androidAppCompatVersion" }
lifecycle-extension = { module = "androidx.lifecycle:lifecycle-extensions", version.ref = "androidLifecycleVersion" }
android-bugly = { module = "com.tencent.bugly:crashreport", version.ref = "bugly" }
permissionX-android = { module = "com.guolindev.permissionx:permissionx", version.ref = "permissionX" }#数据处理
file-picker = { module = "io.github.vinceglb:filekit-compose", version.ref = "file" }
okio-core = { module = "com.squareup.okio:okio", version.ref = "okio" }
datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" }
multiplatform-settings = { module = "com.russhwolf:multiplatform-settings", version.ref = "settings" }
multiplatform-coroutines = { module = "com.russhwolf:multiplatform-settings-coroutines", version.ref = "settings" }
multiplatform-datastore = { module = "com.russhwolf:multiplatform-settings-datastore", version.ref = "settings" }
multiplatform-serialization = { module = "com.russhwolf:multiplatform-settings-serialization", version.ref = "settings" }compose-ui = { module = "androidx.compose.ui:ui", version.ref = "compose" }
compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose" }
compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "compose" }
compose-material3 = { module = "androidx.compose.material3:material3", version.ref = "compose-material3" }#依赖注入
koin-android = { module = "io.insert-koin:koin-android", version.ref = "koin" }
koin-androidx-compose = { module = "io.insert-koin:koin-androidx-compose", version.ref = "koin" }
koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" }
koin-compose = { module = "io.insert-koin:koin-compose", version.ref = "koin" }
stately-common = { module = "co.touchlab:stately-common", version.ref = "stately" }#权限申请,兼容Android
mokopermission = { module = "dev.icerock.moko:permissions", version.ref = "mokopermission" }
mokopermission-compose = { module = "dev.icerock.moko:permissions-compose", version.ref = "mokopermission" }
mokoMvvmCompose = { module = "dev.icerock.moko:mvvm-compose", version.ref = "mokoMvvmVersion" }
precompose-navigator = { module = "moe.tlaster:precompose", version.ref = "precompose" }
precompose-koin = { module = "moe.tlaster:precompose-koin", version.ref = "precompose" }
precompose-viewmodel = { module = "moe.tlaster:precompose-viewmodel", version.ref = "precompose" }
mokoMvvmCore = { module = "dev.icerock.moko:mvvm-core", version.ref = "mokoMvvmVersion" }#数据库
android-driver = { module = "app.cash.sqldelight:android-driver", version.ref = "sqlDelight" }
native-driver = { module = "app.cash.sqldelight:native-driver", version.ref = "sqlDelight" }
sqldelight-extensions = { module = "app.cash.sqldelight:coroutines-extensions", version.ref = "sqlDelight" }
sqlDelight-runtime = { module = "app.cash.sqldelight:runtime", version.ref = "sqlDelight" }
primitive-adapters = { module = "app.cash.sqldelight:primitive-adapters", version.ref = "sqlDelight" }
sqlite-driver = { module = "app.cash.sqldelight:sqlite-driver", version.ref = "sqlDelight" }#ktor 3.0网络连接
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" }
#ktor-client-curl = { module = "io.ktor:ktor-client-curl", version.ref = "ktor" }
#ktor-client-auth = { module = "io.ktor:ktor-client-auth", version.ref = "ktor" }
#ktor engines
#ktor-client-apache = { module = "io.ktor:ktor-client-apache", version.ref = "ktor" }
#ktor-client-jetty = { module = "io.ktor:ktor-client-jetty", version.ref = "ktor" }
ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
ktor-client-android = { module = "io.ktor:ktor-client-android", version.ref = "ktor" }
ktor-client-ios = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" }
ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" }
ktoken = { group = "com.aallam.ktoken", name = "ktoken", version.ref = "ktoken" }
openai-client = { module = "com.aallam.openai:openai-client", version.ref = "openai-client" }#https://coil-kt.github.io/coil/getting_started/
#coil3-core = { module = "io.coil-kt.coil3:coil", version.ref = "coil3" }
coil3-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil3" }
coil3-svg = { module = "io.coil-kt.coil3:coil-svg", version.ref = "coil3" }
coil3-ktor = { module = "io.coil-kt.coil3:coil-network-ktor3", version.ref = "coil3" }
#coil3-ktor = { module = "io.coil-kt.coil3:coil-network-ktor2", version.ref = "coil3" }
#video/gif只有Android版
#coil3-video = { module = "io.coil-kt.coil3:coil-video", version.ref = "coil3" }
#coil3-gif = { module = "io.coil-kt.coil3:coil-gif", version.ref = "coil3" }[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }
androidLibrary = { id = "com.android.library", version.ref = "agp" }
kotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
kotlinCocoapods = { id = "org.jetbrains.kotlin.native.cocoapods", version.ref = "kotlin" }
jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
kotlinxSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
jetbrainsCompose = { id = "org.jetbrains.compose", version.ref = "compose-plugin" }
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
buildKonfig = { id = "com.codingfeline.buildkonfig", version.ref = "buildkonfigGradlePlugin" }
sqlDelight = { id = "app.cash.sqldelight", version.ref = "sqlDelight" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }

MVI+Flow设计

使用//主动获取数据 chatViewModel.processConfig(ModelConfigIntent.LoadData)

package com.hwj.ai.ui.viewmodelimport com.hwj.ai.data.repository.GlobalRepository
import com.hwj.ai.global.CODE_IS_DARK
import com.hwj.ai.global.getCacheBoolean
import com.hwj.ai.global.getMills
import com.hwj.ai.models.LLMModel
import com.hwj.ai.ui.global.GlobalIntent
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import moe.tlaster.precompose.viewmodel.ViewModel
import moe.tlaster.precompose.viewmodel.viewModelScopeclass ChatViewModel(private val globalRepo: GlobalRepository) : ViewModel() {private val _drawerShouldBeOpened = MutableStateFlow(false)val drawerShouldBeOpened = _drawerShouldBeOpened.asStateFlow()fun openDrawer() {_drawerShouldBeOpened.value = true}fun resetOpenDrawerAction() {_drawerShouldBeOpened.value = false}//全局参数状态private val _configObs = MutableStateFlow(ModelConfigState())val configState = _configObs.asStateFlow()private var lastTime = getMills()//本地主题值修改private val _darkObs = MutableStateFlow(false)val darkState = _darkObs.asStateFlow()fun processConfig(intent: ModelConfigIntent) {when (intent) {is ModelConfigIntent.LoadData -> {fetchModelConfig()}is ModelConfigIntent.UpdateData -> {if (getMills() - lastTime > 30000) {fetchModelConfig()}}}}fun processGlobal(intent: GlobalIntent) {when (intent) {is GlobalIntent.CheckDarkTheme -> {fetchDarkStatus()}}}private fun fetchModelConfig() {_configObs.update { it.copy(isLoading = true) }viewModelScope.launch(Dispatchers.IO) {try {val result = globalRepo.fetchModelConfig()_configObs.update { it.copy(isLoading = false, data = result) }} catch (e: Exception) {_configObs.update { it.copy(error = e.toString()) }}}}private fun fetchDarkStatus() {viewModelScope.launch {val isDark = getCacheBoolean(CODE_IS_DARK)_darkObs.value = isDark}}
}data class ModelConfigState(val isLoading: Boolean = false,val data: List<LLMModel>? = null,val error: String? = null
)
//解决不了基类的写法
//data class ModelConfigState(val json: String? = null) : BaseUiState<String>(data = json)sealed class ModelConfigIntent {//获取所有的大模型数据,解析后保存到本地object LoadData : ModelConfigIntent()//判断时间间隔是否需要更新数据,主动拉取,data class UpdateData(val time: Long) : ModelConfigIntent()
} MyState 继承自 UiState
//data class MyState(
//    val items: List<String> = emptyList() // 你可以根据具体需要定制数据类型
//) : BaseUiState<List<String>>(data = items)

富文本显示

//android + windows@Composable
private fun TestBotMsgCard1(message: MessageModel) {
//    val chatViewModel = koinViewModel(ChatViewModel::class)
//    val isDark = chatViewModel.darkState.collectAsState().valueval richTextStyle = RichTextStyle(codeBlockStyle = CodeBlockStyle(textStyle = TextStyle(fontFamily = FontFamily.Default,fontWeight = FontWeight.Normal,fontSize = 13.sp,color = BackCodeTxtColor,),wordWrap = true,modifier = Modifier.background(color = BackCodeGroundColor,shape = RoundedCornerShape(6.dp))),stringStyle = RichTextStringStyle())//第一种
//    com.halilibo.richtext.ui.material.RichText(
//        modifier = Modifier.padding(
//            horizontal = 18.dp,
//            vertical = 12.dp
//        ).background(MaterialTheme.colorScheme.onPrimary),
//        style = richTextStyle,
//
//        ) {
//        //字体颜色对了,但是没能解析富文本的符合
            Text(message.answer.trimIndent(), color = MaterialTheme.colorScheme.onTertiary)
//
//        //没能改字体颜色
//        Markdown(message.answer.trimIndent())
//    }//第二
//    val richTextState = rememberRichTextState()
//    richTextState.setMarkdown(message.answer.trimIndent())
//    richTextState.config.codeSpanBackgroundColor= BackCodeGroundColor
//    richTextState.config.codeSpanColor= BackCodeTxtColor
//    ThemeChatLite {
//        RichTextEditor(
//            modifier = Modifier.padding(
//                horizontal = 18.dp,
//                vertical = 12.dp
//            ).background(MaterialTheme.colorScheme.onPrimary), state = richTextState,
//            textStyle = LocalTextStyle.current.copy(color = MaterialTheme.colorScheme.onTertiary)
//        )//第三
//    val parser =CommonmarkAstNodeParser()
//    RichText( modifier = Modifier.padding(
//        horizontal = 18.dp,
//        vertical = 12.dp
//    ).background(MaterialTheme.colorScheme.onPrimary),
//    style = richTextStyle,
//        ){
//        BasicMarkdown(astNode = parser.parse(message.answer.trimIndent()))
//    }//第四  追踪源码查看 RichTextMaterialTheme-》contentColorProvider 修改内部字体颜色,自定义代码颜色RichTextThemeProvider(contentColorProvider = { MaterialTheme.colorScheme.onTertiary }) {BasicRichText(modifier = Modifier.padding(horizontal = 18.dp,vertical = 12.dp).background(MaterialTheme.colorScheme.onPrimary),style = richTextStyle,) {Markdown(message.answer.trimIndent())}}}
//ios端
//不会屏闪,也可现实代码,但是没有代码框,iOS端只要遇到代码就有线程报错日志
@Composable
fun BotCommonCardApp(message: MessageModel) {val chatViewModel = koinViewModel(ChatViewModel::class)val isDark = chatViewModel.darkState.collectAsState().valueval subScope = rememberCoroutineScope()val state = rememberRichTextState()LaunchedEffect(Unit) {state.removeLink()state.config.codeSpanBackgroundColor = BackCodeGroundColorstate.config.codeSpanColor = BackCodeTxtColorchatViewModel.processGlobal(GlobalIntent.CheckDarkTheme)if (!state.isCodeSpan) {state.toggleCodeSpan()}
//        ```java //无法解析这个 只有  `Code span example` ,但是3点是代码块,一点是行内代码,}val answerState = remember { mutableStateOf("") }LaunchedEffect(message.answer.trimIndent()) {subScope.launch(Dispatchers.IO) { //貌似频繁IOval newMsg = message.answer.trimIndent().replace("```java", "`").replace("```", "`")answerState.value = newMsg}}RichText(state = state.apply {state.setMarkdown(answerState.value)},modifier = Modifier.padding(horizontal = 18.dp, vertical = 12.dp).background(MaterialTheme.colorScheme.onPrimary),color = MaterialTheme.colorScheme.onTertiary,style = TextStyle(fontFamily = FontFamily.Default,fontWeight = FontWeight.Normal,fontSize = 13.sp,color = MaterialTheme.colorScheme.onTertiary))
}

总结

  大模型的多轮对话实现总体不难,只是很多小问题,现在我只是实现了主体对话功能,把多端都跑起来了,进而熟悉整个MVI的开发设计,以前对依赖注入Koin不够重视觉得没啥应用,在声明式开发中现在非常好用,绝大数工具实体都不用new对象,而且借助remember和flow做到状态随处感知,界面渲染代码 少很多,后续引入各种库的高级用法优化代码结构。现在是AI大火,跨端开发AI应用探索下这技术路线的可行性,过于先进确实很多问题只能自己解决,多用chatgpt给点思路也行。
  kotlin multiplatform 开发框架最大好处是直接用kotlin实现跨端逻辑代码,再结合compose multiplatform框架把UI框架也补全了,不需要像Futter要引入两个渲染引擎,一个引擎使用 C/C++ 开发,直接调用 OpenGL/Skia 的 API 进行绘制,从而摆脱 iOS 的 UIKit 以及 Android 的 View 组件直接渲染成需要的样式,保证样式高度统一,另一个是 Dart 语言的 Runtime,用于解析并运行 Dart 语言编译的 Bundle,导致apk包很大。今年的编译器IDEA也支持运行开发kotlin multiplatform项目,Fleet编译器不整了,目前生态库也不断加入比上一年开始完善很多,前期配置好后面的编译就很省事,前期的拉库是个漫长等待。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/28059.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

VLM-E2E:通过多模态驾驶员注意融合增强端到端自动驾驶

25年2月来自香港科大广州分校、理想汽车和厦门大学的论文“VLM-E2E: Enhancing End-to-End Autonomous Driving with Multimodal Driver Attention Fusion”。 人类驾驶员能够利用丰富的注意语义&#xff0c;熟练地应对复杂场景&#xff0c;但当前的自动驾驶系统难以复制这种能…

Linux的进程观:简单性如何成就强大性(三)

1. 环境变量 1.1. 基本概念 环境变量(environment variables)⼀般是指在操作系统中⽤来指定操作系统运⾏环境的⼀些参数。 如&#xff1a;我们在编写C/C代码的时候&#xff0c;在链接的时候&#xff0c;从来不知道我们的所链接的动态静态库在哪⾥&#xff0c;但是照样可以链接…

广域互联网关键技术详解(GRE/LSTP/IPsec/NAT/SAC/SPR)

《广域互联网关键技术详解》属于博主的“广域网”专栏&#xff0c;若想成为HCIE&#xff0c;对于广域网相关的知识需要非常了解&#xff0c;更多关于广域网的内容博主会更新在“广域网”专栏里&#xff0c;请持续关注&#xff01; 一.前言 广域互联技术纷杂多样&#xff0c;不…

论文阅读笔记:UniFace: Unified Cross-Entropy Loss for Deep Face Recognition

论文阅读笔记&#xff1a;UniFace: Unified Cross-Entropy Loss for Deep Face Recognition 1 背景2 创新点3 方法3.1 回顾softmax损失3.2 统一交叉熵损失3.3 人脸验证中的UCE损失3.4 进一步的优化3.4.1 边际UCE损失3.4.2 平衡BCE损失 4 实验4.1 消融实验4.2 和SOTA方法对比 论…

DeepSeek崛起:如何在云端快速部署你的专属AI助手

在2025年春节的科技盛宴上&#xff0c;DeepSeek因其在AI领域的卓越表现成为焦点&#xff0c;其开源的推理模型DeepSeek-R1擅长处理多种复杂任务&#xff0c;支持多语言处理&#xff0c;并通过搜索引擎获取实时信息。DeepSeek因其先进的自然语言处理技术、广泛的知识库和高性价比…

LLM大型语言模型(一)

1. 什么是 LLM&#xff1f; LLM&#xff08;大型语言模型&#xff09;是一种神经网络&#xff0c;专门用于理解、生成并对人类文本作出响应。这些模型是深度神经网络&#xff0c;通常训练于海量文本数据上&#xff0c;有时甚至覆盖了整个互联网的公开文本。 LLM 中的 “大” …

石基大商:OceanBase + Flink CDC,搭建连锁零售系统数据湖

本文作者&#xff1a;白剑&#xff0c;石基大商连锁事业部架构组 石基大商连锁事业部专注于连锁零售软件&#xff0c;为企业提供ERP解决方案。石基在零售行业拥有众多知名品牌客户&#xff0c;如华润万家、永旺、永辉和联华等&#xff0c;并与很多地方性零售企业紧密合作。而对…

LeetCode 分割回文串(回溯、dp)

131.分割回文串 给你一个字符串 s&#xff0c;请你将 s 分割成一些 子串&#xff0c;使每个子串都是 回文串 。返回 s 所有可能的分割方案。 示例 1&#xff1a; 输入&#xff1a;s "aab" 输出&#xff1a;[["a","a","b"],["a…

好数——前缀和思想(题目分享)

今天我的舍友去参加“传智杯”广东省的省赛&#xff0c;跟我说了这样一道题&#xff0c;他说他想不出来怎么去优化代码&#xff0c;怎么做都是套用两层for循环超时&#xff0c;下面我就根据题意&#xff0c;使用前缀和的算法去优化一下思路&#xff0c;题目本身是不难的&#x…

记录uniapp小程序对接腾讯IM即时通讯无ui集成(2)

完成以上步骤之后开始进行登录&#xff0c;登陆就需要账号。这个账号我们可以在腾讯云中创建。 有了账号之后开始去小程序进行登陆操作。腾讯云接口文档 这里除了帐号还需要一个校验值userSig正常项目开发这个字段可以在登陆后让后端返回&#xff0c;现在是测试我们直接去控制…

智能指针的使用和原理

目录 C标准库智能指针的使用 auto_ptr的了解 unique_ptr的了解 shared_ptr的了解 应用 析构问题 解决办法 方案一&#xff1a;特化 方案二&#xff1a;删除器 智能指针原理 shared_ptr循环引用问题 了解weak_ptr shared_ptr的线程安全问题 内存泄漏 如何避免内存…

【北上广深杭大厂AI算法面试题】深度学习篇...这里详细说明ResNet中为什么不用dropout?

【北上广深杭大厂AI算法面试题】深度学习篇…这里详细说明ResNet中为什么不用dropout? 【北上广深杭大厂AI算法面试题】深度学习篇…这里详细说明ResNet中为什么不用dropout? 文章目录 【北上广深杭大厂AI算法面试题】深度学习篇...这里详细说明ResNet中为什么不用dropout?…

stm32移植LCD2002驱动

介绍 LCD2002支持20X2个字符串显示&#xff0c;引脚功能和读写时序跟LCD1602都很像 LCD类型&#xff1a;字符点阵 点 阵 数&#xff1a;202 外形尺寸&#xff1a;116.0mm37.0mm&#xff08;长宽&#xff09; 视域尺寸&#xff1a;83.0mm18.6mm 点 距 离&#xff1a;0.05mm…

*动态规划(4)

持续更新 1.入门 ⽤于解决多阶段决策问题的算法思想。它通过将复杂问题分解为更⼩的⼦问题&#xff0c;并存储⼦问题的解&#xff08;通常称为“状态”&#xff09;&#xff0c;从⽽避免重复计算&#xff0c;提⾼效率。因此&#xff0c;动态规划⾥&#xff0c;蕴含着分治与剪枝…

计算机毕业设计SpringBoot+Vue.js社团管理系统(源码+文档+PPT+讲解)

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

现今大语言模型性能(准确率)比较

现今大语言模型性能(准确率)比较 表头信息:表的标题为“大语言模型性能比较结果”(英文:Table 1: Large Language Model Performance Comparison Results),表明该表是用于对比不同大语言模型的性能。列信息: 模型:列出参与比较的不同大语言模型名称,包括LLAMA3(70B)…

合成复用原则

合成复用原则 也被称为组合复用原则或聚合复用原则。 合成复用原则提倡尽量使用组合或者聚合等关联关系来实现代码复用&#xff0c;而不是通过继承关系来复用代码。组合是一种强的 “拥有” 关系&#xff0c;体现了严格的部分和整体的关系&#xff0c;部分和整体的生命周期一…

Unity 对象池技术

介绍 是什么&#xff1f; 在开始时初始化若干对象&#xff0c;将它们存到对象池中。需要使用的时候从对象池中取出&#xff0c;使用完后重新放回对象池中。 优点 可以避免频繁创建和销毁对象带来性能消耗。 适用场景 如果需要对某种对象进行频繁创建和销毁时&#xff0c;例…

记一次ScopeSentry搭建

介绍 Scope Sentry是一款具有资产测绘、子域名枚举、信息泄露检测、漏洞扫描、目录扫描、子域名接管、爬虫、页面监控功能的工具&#xff0c;通过构建多个节点&#xff0c;自由选择节点运行扫描任务。当出现新漏洞时可以快速排查关注资产是否存在相关组件。 目前功能 插件系…

LeetCode热题100JS(20/100)第四天|​41. 缺失的第一个正数​|​73. 矩阵置零​|​54. 螺旋矩阵​|​48. 旋转图像​

41. 缺失的第一个正数 题目链接&#xff1a;41. 缺失的第一个正数 难度&#xff1a;困难 刷题状态&#xff1a;1刷 新知识&#xff1a; 解题过程 思考 示例 1&#xff1a; 输入&#xff1a;nums [1,2,0] 输出&#xff1a;3 解释&#xff1a;范围 [1,2] 中的数字都在数组中…