Make new chat creation lazy
This commit is contained in:
@@ -21,6 +21,14 @@ final class ChatExperiencePolicy {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static String firstChatTitle(String content) {
|
||||||
|
String clean = content == null ? "" : content.trim();
|
||||||
|
if (clean.isEmpty()) return "";
|
||||||
|
StringBuilder title = new StringBuilder();
|
||||||
|
clean.codePoints().limit(15).forEach(title::appendCodePoint);
|
||||||
|
return title.toString();
|
||||||
|
}
|
||||||
|
|
||||||
static boolean requiresTitleBeforeEnteringChat() {
|
static boolean requiresTitleBeforeEnteringChat() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -399,6 +399,16 @@ private data class PilotChatSessionsData(
|
|||||||
val agents: List<Models.Agent>,
|
val agents: List<Models.Agent>,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private fun localDraftChatSession(agent: Models.Agent): Models.ChatSession =
|
||||||
|
Models.ChatSession(
|
||||||
|
JSONObject()
|
||||||
|
.put("id", "")
|
||||||
|
.put("title", "")
|
||||||
|
.put("agent_id", agent.id)
|
||||||
|
.put("status", "draft")
|
||||||
|
.put("updated_at", ""),
|
||||||
|
)
|
||||||
|
|
||||||
private data class PilotProjectDetailData(
|
private data class PilotProjectDetailData(
|
||||||
val project: Models.Project,
|
val project: Models.Project,
|
||||||
val issues: List<Models.Issue>,
|
val issues: List<Models.Issue>,
|
||||||
@@ -3007,7 +3017,6 @@ private fun PilotChatSessions(
|
|||||||
var state by remember(workspaceId) { mutableStateOf<Result<PilotChatSessionsData>?>(null) }
|
var state by remember(workspaceId) { mutableStateOf<Result<PilotChatSessionsData>?>(null) }
|
||||||
var refresh by remember(workspaceId) { mutableIntStateOf(0) }
|
var refresh by remember(workspaceId) { mutableIntStateOf(0) }
|
||||||
var showAllSessions by remember(workspaceId) { mutableStateOf(false) }
|
var showAllSessions by remember(workspaceId) { mutableStateOf(false) }
|
||||||
var creating by remember(workspaceId) { mutableStateOf(false) }
|
|
||||||
var deletingSessionId by remember(workspaceId) { mutableStateOf<String?>(null) }
|
var deletingSessionId by remember(workspaceId) { mutableStateOf<String?>(null) }
|
||||||
var actionText by remember(workspaceId) { mutableStateOf<String?>(null) }
|
var actionText by remember(workspaceId) { mutableStateOf<String?>(null) }
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
@@ -3028,29 +3037,14 @@ private fun PilotChatSessions(
|
|||||||
state = loaded
|
state = loaded
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createSession(data: PilotChatSessionsData) {
|
fun openDraftSession(data: PilotChatSessionsData) {
|
||||||
if (creating) return
|
|
||||||
val agent = data.agents.firstOrNull()
|
val agent = data.agents.firstOrNull()
|
||||||
if (agent == null) {
|
if (agent == null) {
|
||||||
actionText = if (zh) "请先创建 Agent" else "Create an agent first"
|
actionText = if (zh) "请先创建 Agent" else "Create an agent first"
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val sessionTitle = ChatExperiencePolicy.newChatInitialTitle()
|
|
||||||
creating = true
|
|
||||||
actionText = null
|
actionText = null
|
||||||
scope.launch {
|
onSessionClick(localDraftChatSession(agent))
|
||||||
val result = withContext(Dispatchers.IO) {
|
|
||||||
runCatching { api.createChatSession(workspaceId, agent.id, sessionTitle) }
|
|
||||||
}
|
|
||||||
creating = false
|
|
||||||
result.onSuccess { session ->
|
|
||||||
actionText = if (zh) "聊天会话已创建" else "Chat session created"
|
|
||||||
refresh++
|
|
||||||
onSessionClick(session)
|
|
||||||
}.onFailure {
|
|
||||||
actionText = "${if (zh) "创建聊天失败" else "Create chat failed"}: ${it.message ?: it.toString()}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteSession(session: Models.ChatSession) {
|
fun deleteSession(session: Models.ChatSession) {
|
||||||
@@ -3096,13 +3090,12 @@ private fun PilotChatSessions(
|
|||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
MulticaPillButton(
|
MulticaPillButton(
|
||||||
text = if (creating) "..." else if (zh) "新聊天" else "New Chat",
|
text = if (zh) "新聊天" else "New Chat",
|
||||||
onClick = {
|
onClick = {
|
||||||
createSession(data)
|
openDraftSession(data)
|
||||||
actionText = null
|
actionText = null
|
||||||
},
|
},
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
enabled = !creating,
|
|
||||||
tone = MulticaButtonTone.Primary,
|
tone = MulticaButtonTone.Primary,
|
||||||
contentDescription = "Chat New Session Toggle",
|
contentDescription = "Chat New Session Toggle",
|
||||||
)
|
)
|
||||||
@@ -3419,6 +3412,7 @@ private fun PilotChatMessages(
|
|||||||
onSessionChanged: (Models.ChatSession) -> Unit,
|
onSessionChanged: (Models.ChatSession) -> Unit,
|
||||||
onArchived: () -> Unit,
|
onArchived: () -> Unit,
|
||||||
) {
|
) {
|
||||||
|
val isDraftSession = session.id.isBlank()
|
||||||
var state by remember(session.id) { mutableStateOf<Result<PilotChatMessagesData>?>(null) }
|
var state by remember(session.id) { mutableStateOf<Result<PilotChatMessagesData>?>(null) }
|
||||||
var messagesRefresh by remember(session.id) { mutableIntStateOf(0) }
|
var messagesRefresh by remember(session.id) { mutableIntStateOf(0) }
|
||||||
var pendingRefresh by remember(session.id) { mutableIntStateOf(0) }
|
var pendingRefresh by remember(session.id) { mutableIntStateOf(0) }
|
||||||
@@ -3432,6 +3426,7 @@ private fun PilotChatMessages(
|
|||||||
var agentPickerOpen by remember(session.id) { mutableStateOf(false) }
|
var agentPickerOpen by remember(session.id) { mutableStateOf(false) }
|
||||||
var agentQuery by remember(session.id) { mutableStateOf("") }
|
var agentQuery by remember(session.id) { mutableStateOf("") }
|
||||||
var switchingAgentId by remember(session.id) { mutableStateOf<String?>(null) }
|
var switchingAgentId by remember(session.id) { mutableStateOf<String?>(null) }
|
||||||
|
var draftAgentId by remember(session.id, session.agentId) { mutableStateOf(session.agentId) }
|
||||||
var composerFocused by remember(session.id) { mutableStateOf(false) }
|
var composerFocused by remember(session.id) { mutableStateOf(false) }
|
||||||
var actionError by remember(session.id) { mutableStateOf<String?>(null) }
|
var actionError by remember(session.id) { mutableStateOf<String?>(null) }
|
||||||
val configuration = LocalConfiguration.current
|
val configuration = LocalConfiguration.current
|
||||||
@@ -3448,12 +3443,19 @@ private fun PilotChatMessages(
|
|||||||
val previous = state
|
val previous = state
|
||||||
val result = withContext(Dispatchers.IO) {
|
val result = withContext(Dispatchers.IO) {
|
||||||
runCatching {
|
runCatching {
|
||||||
val messages = api.chatMessages(workspaceId, session.id)
|
|
||||||
val pending = runCatching { api.pendingChatTask(workspaceId, session.id) }
|
|
||||||
.getOrElse { Models.ChatPendingTask(org.json.JSONObject()) }
|
|
||||||
val agents = api.agents(workspaceId)
|
val agents = api.agents(workspaceId)
|
||||||
.filter { it.archivedAt.isBlank() }
|
.filter { it.archivedAt.isBlank() }
|
||||||
.sortedBy { it.name.lowercase() }
|
.sortedBy { it.name.lowercase() }
|
||||||
|
if (isDraftSession) {
|
||||||
|
return@runCatching PilotChatMessagesData(
|
||||||
|
emptyList(),
|
||||||
|
Models.ChatPendingTask(org.json.JSONObject()),
|
||||||
|
agents,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val messages = api.chatMessages(workspaceId, session.id)
|
||||||
|
val pending = runCatching { api.pendingChatTask(workspaceId, session.id) }
|
||||||
|
.getOrElse { Models.ChatPendingTask(org.json.JSONObject()) }
|
||||||
runCatching { api.markChatSessionRead(workspaceId, session.id) }
|
runCatching { api.markChatSessionRead(workspaceId, session.id) }
|
||||||
PilotChatMessagesData(messages, pending, agents)
|
PilotChatMessagesData(messages, pending, agents)
|
||||||
}
|
}
|
||||||
@@ -3466,6 +3468,7 @@ private fun PilotChatMessages(
|
|||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(session.id, pendingRefresh) {
|
LaunchedEffect(session.id, pendingRefresh) {
|
||||||
|
if (isDraftSession) return@LaunchedEffect
|
||||||
val current = state?.getOrNull() ?: return@LaunchedEffect
|
val current = state?.getOrNull() ?: return@LaunchedEffect
|
||||||
val previousPendingTaskId = current.pendingTask.taskId
|
val previousPendingTaskId = current.pendingTask.taskId
|
||||||
val result = withContext(Dispatchers.IO) {
|
val result = withContext(Dispatchers.IO) {
|
||||||
@@ -3523,6 +3526,10 @@ private fun PilotChatMessages(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun archiveSession() {
|
fun archiveSession() {
|
||||||
|
if (isDraftSession) {
|
||||||
|
onArchived()
|
||||||
|
return
|
||||||
|
}
|
||||||
if (archiving) return
|
if (archiving) return
|
||||||
archiving = true
|
archiving = true
|
||||||
headerMenuOpen = false
|
headerMenuOpen = false
|
||||||
@@ -3558,6 +3565,13 @@ private fun PilotChatMessages(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun switchAgent(agent: Models.Agent) {
|
fun switchAgent(agent: Models.Agent) {
|
||||||
|
if (isDraftSession) {
|
||||||
|
draftAgentId = agent.id
|
||||||
|
agentPickerOpen = false
|
||||||
|
agentQuery = ""
|
||||||
|
actionError = null
|
||||||
|
return
|
||||||
|
}
|
||||||
if (agent.id == session.agentId || switchingAgentId != null) {
|
if (agent.id == session.agentId || switchingAgentId != null) {
|
||||||
agentPickerOpen = false
|
agentPickerOpen = false
|
||||||
return
|
return
|
||||||
@@ -3601,7 +3615,11 @@ private fun PilotChatMessages(
|
|||||||
.semantics { contentDescription = "Chat Detail Header Safe Horizontal Insets" },
|
.semantics { contentDescription = "Chat Detail Header Safe Horizontal Insets" },
|
||||||
) {
|
) {
|
||||||
PilotPageHeader(
|
PilotPageHeader(
|
||||||
title = session.title,
|
title = if (isDraftSession) {
|
||||||
|
if (zh) "新聊天" else "New Chat"
|
||||||
|
} else {
|
||||||
|
session.title
|
||||||
|
},
|
||||||
leading = { PilotSecondaryBackButton(zh = zh, onBack = onBack) },
|
leading = { PilotSecondaryBackButton(zh = zh, onBack = onBack) },
|
||||||
trailing = {
|
trailing = {
|
||||||
MulticaIconPillButton(
|
MulticaIconPillButton(
|
||||||
@@ -3618,7 +3636,7 @@ private fun PilotChatMessages(
|
|||||||
MulticaCupertinoActionSheetItem(
|
MulticaCupertinoActionSheetItem(
|
||||||
title = if (archiving) "..." else if (zh) "归档会话" else "Archive Session",
|
title = if (archiving) "..." else if (zh) "归档会话" else "Archive Session",
|
||||||
icon = Icons.Outlined.Archive,
|
icon = Icons.Outlined.Archive,
|
||||||
enabled = !archiving,
|
enabled = !isDraftSession && !archiving,
|
||||||
contentDescription = "Chat Header Cupertino Action Sheet Archive Session",
|
contentDescription = "Chat Header Cupertino Action Sheet Archive Session",
|
||||||
onClick = { archiveSession() },
|
onClick = { archiveSession() },
|
||||||
),
|
),
|
||||||
@@ -3652,7 +3670,8 @@ private fun PilotChatMessages(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
loaded?.getOrNull()?.let { chatData ->
|
loaded?.getOrNull()?.let { chatData ->
|
||||||
val activeAgent = chatData.agents.firstOrNull { it.id == session.agentId } ?: chatData.agents.firstOrNull()
|
val selectedAgentId = if (isDraftSession) draftAgentId else session.agentId
|
||||||
|
val activeAgent = chatData.agents.firstOrNull { it.id == selectedAgentId } ?: chatData.agents.firstOrNull()
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@@ -3832,18 +3851,41 @@ private fun PilotChatMessages(
|
|||||||
onClick = {
|
onClick = {
|
||||||
val content = draft.trim()
|
val content = draft.trim()
|
||||||
if (content.isEmpty() || sending) return@MulticaIconPillButton
|
if (content.isEmpty() || sending) return@MulticaIconPillButton
|
||||||
|
val targetAgentId = if (isDraftSession) {
|
||||||
|
draftAgentId.ifBlank { state?.getOrNull()?.agents?.firstOrNull()?.id.orEmpty() }
|
||||||
|
} else {
|
||||||
|
session.agentId
|
||||||
|
}
|
||||||
|
if (isDraftSession && targetAgentId.isBlank()) {
|
||||||
|
sendError = if (zh) "请先创建 Agent" else "Create an agent first"
|
||||||
|
return@MulticaIconPillButton
|
||||||
|
}
|
||||||
sending = true
|
sending = true
|
||||||
locallyPending = true
|
locallyPending = true
|
||||||
sendError = null
|
sendError = null
|
||||||
scope.launch {
|
scope.launch {
|
||||||
val result = withContext(Dispatchers.IO) {
|
val result = withContext(Dispatchers.IO) {
|
||||||
runCatching { api.sendChatMessage(workspaceId, session.id, content) }
|
runCatching {
|
||||||
|
if (isDraftSession) {
|
||||||
|
val title = ChatExperiencePolicy.firstChatTitle(content)
|
||||||
|
val created = api.createChatSession(workspaceId, targetAgentId, title)
|
||||||
|
api.sendChatMessage(workspaceId, created.id, content)
|
||||||
|
created
|
||||||
|
} else {
|
||||||
|
api.sendChatMessage(workspaceId, session.id, content)
|
||||||
|
session
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
sending = false
|
sending = false
|
||||||
result.onSuccess {
|
result.onSuccess { created ->
|
||||||
draft = ""
|
draft = ""
|
||||||
|
if (isDraftSession) {
|
||||||
|
onSessionChanged(created)
|
||||||
|
} else {
|
||||||
pendingRefresh++
|
pendingRefresh++
|
||||||
messagesRefresh++
|
messagesRefresh++
|
||||||
|
}
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
locallyPending = false
|
locallyPending = false
|
||||||
sendError = "${if (zh) "发送失败" else "Send failed"}: ${it.message ?: it.toString()}"
|
sendError = "${if (zh) "发送失败" else "Send failed"}: ${it.message ?: it.toString()}"
|
||||||
|
|||||||
Reference in New Issue
Block a user