Compare commits
No commits in common. "9ed1e317001a233d07179516b837ef41b0c2795a" and "62cb78167d84a75567634ecf95cd1be7d404f169" have entirely different histories.
9ed1e31700
...
62cb78167d
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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()),
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
Loading…
Reference in New Issue