Compare commits

..

No commits in common. "9ed1e317001a233d07179516b837ef41b0c2795a" and "62cb78167d84a75567634ecf95cd1be7d404f169" have entirely different histories.

4 changed files with 54 additions and 55 deletions

View File

@ -21,7 +21,7 @@ import org.json.JSONArray
import org.json.JSONObject
import java.util.UUID
private const val STREAM_FLUSH_INTERVAL_MS = 150L
private const val STREAM_FLUSH_INTERVAL_MS = 50L
class ChatViewModel(application: Application) : AndroidViewModel(application) {
private val repository = AiChatRepository()
@ -139,8 +139,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
}
private fun saveCacheToLocal() {
// 将数据保存放到IO线程中防止SharedPreferences写大文本阻塞主线程卡顿
viewModelScope.launch(kotlinx.coroutines.Dispatchers.IO) {
try {
// 保存会话列表
val sessionsArray = JSONArray()
@ -171,7 +169,6 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
e.printStackTrace()
}
}
}
// 👈 用户手动中断当前正在生成的 AI 回答的方法!
fun cancelActiveStreaming() {

View File

@ -94,19 +94,25 @@ fun FloatingChatWidget() {
visible = true
}
// 👈 用 FAB 位置计算对话框位置,但强制钳制在屏幕内
// FAB右下角为基准对话框应该出现在 FAB 附近
val fabRight = screenWidthPx - paddingPx * 2 + fabOffsetX // FAB右边缘
val fabBottom = screenHeightPx - paddingPx * 2 - navBarPx + fabOffsetY // FAB下边缘
// 对话框优先放在 FAB 左上方,如果放不下就贴边
val rawX = fabRight - cardWidthPx - paddingPx // FAB左边
val rawY = fabBottom - cardHeightPx - paddingPx // FAB上边
// 👈 关键:钳制到屏幕内
val clampedX = rawX.coerceIn(0f, screenWidthPx - cardWidthPx)
val clampedY = rawY.coerceIn(0f, screenHeightPx - cardHeightPx)
AnimatedVisibility(
visible = visible,
enter = fadeIn(animationSpec = tween(300)) + scaleIn(initialScale = 0.8f, animationSpec = tween(300)),
exit = fadeOut(animationSpec = tween(200)) + scaleOut(targetScale = 0.8f, animationSpec = tween(200)),
modifier = Modifier
.offset {
val fabRight = screenWidthPx - paddingPx * 2 + fabOffsetX
val fabBottom = screenHeightPx - paddingPx * 2 - navBarPx + fabOffsetY
val rawX = fabRight - cardWidthPx - paddingPx
val rawY = fabBottom - cardHeightPx - paddingPx
val clampedX = rawX.coerceIn(0f, screenWidthPx - cardWidthPx)
val clampedY = rawY.coerceIn(0f, screenHeightPx - cardHeightPx)
IntOffset(
(clampedX + cardDragX).roundToInt()
.coerceIn(0, (screenWidthPx - cardWidthPx).toInt()),

View File

@ -40,18 +40,6 @@ fun ChatArea(
}
}
val context = androidx.compose.ui.platform.LocalContext.current
val markwon = remember(context) {
io.noties.markwon.Markwon.builder(context)
.usePlugin(io.noties.markwon.core.CorePlugin.create())
.usePlugin(io.noties.markwon.ext.tables.TablePlugin.create(context))
.usePlugin(io.noties.markwon.html.HtmlPlugin.create())
.usePlugin(io.noties.markwon.linkify.LinkifyPlugin.create())
.usePlugin(io.noties.markwon.ext.strikethrough.StrikethroughPlugin.create())
.usePlugin(io.noties.markwon.ext.tasklist.TaskListPlugin.create(context))
.build()
}
val isAnyStreaming = remember(messages) { messages.any { it.isStreaming } }
if (isAnyStreaming) {
val totalLength = messages.map { it.content.length }.sum()
@ -123,7 +111,6 @@ fun ChatArea(
val contentWithCursor = msg.content + if (msg.isStreaming) "" else ""
MarkwonRenderer(
markdown = contentWithCursor,
markwon = markwon,
modifier = Modifier.padding(12.dp)
)
}
@ -174,7 +161,19 @@ fun ChatArea(
}
@Composable
fun MarkwonRenderer(markdown: String, markwon: io.noties.markwon.Markwon, modifier: Modifier) {
fun MarkwonRenderer(markdown: String, modifier: Modifier) {
val context = androidx.compose.ui.platform.LocalContext.current
val markwon = remember(context) {
io.noties.markwon.Markwon.builder(context)
.usePlugin(io.noties.markwon.core.CorePlugin.create())
.usePlugin(io.noties.markwon.ext.tables.TablePlugin.create(context))
.usePlugin(io.noties.markwon.html.HtmlPlugin.create())
.usePlugin(io.noties.markwon.linkify.LinkifyPlugin.create())
.usePlugin(io.noties.markwon.ext.strikethrough.StrikethroughPlugin.create())
.usePlugin(io.noties.markwon.ext.tasklist.TaskListPlugin.create(context))
.build()
}
androidx.compose.ui.viewinterop.AndroidView(
factory = { ctx ->
val textView = SafeSelectableTextView(ctx).apply {

View File

@ -187,9 +187,6 @@ object LogManager {
private val fileNameFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
// 过滤掉高频的 VERBOSE 级别日志写入(例如录音帧),保留在控制台输出
if (priority == android.util.Log.VERBOSE) return
executor.execute {
try {
val logFile = File(logDir, "${fileNameFormat.format(Date())}.log")