Skip to content

Commit eb0d669

Browse files
committed
feat(chatdb): add ChatDB agent for text-to-SQL interactions
- Add CHAT_DB agent type to AgentType enum - Create ChatDBPage with split layout (DataSourcePanel + ChatDBChatPane) - Create ChatDBViewModel for managing data sources and chat - Create DataSourcePanel for managing database connections - Create DataSourceConfigDialog for adding/editing data sources - Create ChatDBChatPane for chat interactions - Add Database and Schema icons to AutoDevComposeIcons - Update AgentInterfaceRouter to route to ChatDBPage - Update when expressions in AgentChatInterface, TopBarMenuDesktop, TopBarMenuMobile Closes #508
1 parent f7ea233 commit eb0d669

File tree

17 files changed

+1589
-3
lines changed

17 files changed

+1589
-3
lines changed

mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/AgentType.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ package cc.unitmesh.agent
77
* - LOCAL: Simple local chat mode without heavy tooling
88
* - CODING: Local coding agent with full tool access (file system, shell, etc.)
99
* - CODE_REVIEW: Dedicated code review agent with git integration
10+
* - KNOWLEDGE: Document reader mode for AI-native document reading
11+
* - CHAT_DB: Database chat mode for text-to-SQL interactions
1012
* - REMOTE: Remote agent connected to mpp-server
1113
*/
1214
enum class AgentType {
@@ -30,6 +32,11 @@ enum class AgentType {
3032
*/
3133
KNOWLEDGE,
3234

35+
/**
36+
* Database chat mode - text-to-SQL agent for database queries
37+
*/
38+
CHAT_DB,
39+
3340
/**
3441
* Remote agent mode - connects to remote mpp-server for distributed execution
3542
*/
@@ -40,6 +47,7 @@ enum class AgentType {
4047
CODING -> "Agentic"
4148
CODE_REVIEW -> "Review"
4249
KNOWLEDGE -> "Knowledge"
50+
CHAT_DB -> "ChatDB"
4351
REMOTE -> "Remote"
4452
}
4553

@@ -51,6 +59,7 @@ enum class AgentType {
5159
"coding" -> CODING
5260
"codereview" -> CODE_REVIEW
5361
"documentreader", "documents" -> KNOWLEDGE
62+
"chatdb", "database" -> CHAT_DB
5463
else -> LOCAL_CHAT
5564
}
5665
}

mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/components/header/IdeaAgentTabsHeader.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ private fun getAgentTypeColor(type: AgentType): Color = when (type) {
256256
AgentType.CODING -> IdeaAutoDevColors.Blue.c400
257257
AgentType.CODE_REVIEW -> IdeaAutoDevColors.Indigo.c400
258258
AgentType.KNOWLEDGE -> IdeaAutoDevColors.Green.c400
259+
AgentType.CHAT_DB -> IdeaAutoDevColors.Cyan.c400
259260
AgentType.REMOTE -> IdeaAutoDevColors.Amber.c400
260261
AgentType.LOCAL_CHAT -> JewelTheme.globalColors.text.normal
261262
}
@@ -267,6 +268,7 @@ private fun getAgentTypeIcon(type: AgentType): ImageVector = when (type) {
267268
AgentType.CODING -> IdeaComposeIcons.Code
268269
AgentType.CODE_REVIEW -> IdeaComposeIcons.Review
269270
AgentType.KNOWLEDGE -> IdeaComposeIcons.Book
271+
AgentType.CHAT_DB -> IdeaComposeIcons.Database
270272
AgentType.REMOTE -> IdeaComposeIcons.Cloud
271273
AgentType.LOCAL_CHAT -> IdeaComposeIcons.Chat
272274
}

mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaAgentViewModel.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@ class IdeaAgentViewModel(
249249
AgentType.CODING -> "Coding"
250250
AgentType.CODE_REVIEW -> "CodeReview"
251251
AgentType.KNOWLEDGE -> "Documents"
252+
AgentType.CHAT_DB -> "ChatDB"
252253
}
253254

254255
AutoDevConfigWrapper.saveAgentTypePreference(typeString)

mpp-idea/src/main/kotlin/cc/unitmesh/devins/idea/toolwindow/IdeaComposeIcons.kt

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1738,5 +1738,60 @@ object IdeaComposeIcons {
17381738
}.build()
17391739
}
17401740

1741+
/**
1742+
* Database icon (cylinder shape representing database storage)
1743+
*/
1744+
val Database: ImageVector by lazy {
1745+
ImageVector.Builder(
1746+
name = "Database",
1747+
defaultWidth = 24.dp,
1748+
defaultHeight = 24.dp,
1749+
viewportWidth = 24f,
1750+
viewportHeight = 24f
1751+
).apply {
1752+
path(
1753+
fill = SolidColor(Color.Black)
1754+
) {
1755+
// Database cylinder icon (Storage icon)
1756+
moveTo(2f, 20f)
1757+
horizontalLineToRelative(20f)
1758+
verticalLineToRelative(-4f)
1759+
horizontalLineTo(2f)
1760+
verticalLineToRelative(4f)
1761+
close()
1762+
moveTo(4f, 17f)
1763+
horizontalLineToRelative(2f)
1764+
verticalLineToRelative(2f)
1765+
horizontalLineTo(4f)
1766+
verticalLineToRelative(-2f)
1767+
close()
1768+
moveTo(2f, 4f)
1769+
verticalLineToRelative(4f)
1770+
horizontalLineToRelative(20f)
1771+
verticalLineTo(4f)
1772+
horizontalLineTo(2f)
1773+
close()
1774+
moveTo(6f, 7f)
1775+
horizontalLineTo(4f)
1776+
verticalLineTo(5f)
1777+
horizontalLineToRelative(2f)
1778+
verticalLineToRelative(2f)
1779+
close()
1780+
moveTo(2f, 14f)
1781+
horizontalLineToRelative(20f)
1782+
verticalLineToRelative(-4f)
1783+
horizontalLineTo(2f)
1784+
verticalLineToRelative(4f)
1785+
close()
1786+
moveTo(4f, 11f)
1787+
horizontalLineToRelative(2f)
1788+
verticalLineToRelative(2f)
1789+
horizontalLineTo(4f)
1790+
verticalLineToRelative(-2f)
1791+
close()
1792+
}
1793+
}.build()
1794+
}
1795+
17411796
}
17421797

mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/AutoDevApp.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,10 @@ private fun AutoDevContent(
175175
val typeString = when (type) {
176176
AgentType.REMOTE -> "Remote"
177177
AgentType.LOCAL_CHAT -> "Local"
178-
else -> "Local"
178+
AgentType.CODING -> "Coding"
179+
AgentType.CODE_REVIEW -> "CodeReview"
180+
AgentType.KNOWLEDGE -> "Documents"
181+
AgentType.CHAT_DB -> "ChatDB"
179182
}
180183
AutoDevConfigWrapper.saveAgentTypePreference(typeString)
181184
} catch (e: Exception) {

mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/AgentChatInterface.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,8 +198,9 @@ fun AgentChatInterface(
198198
}
199199

200200
AgentType.CODE_REVIEW,
201-
AgentType.KNOWLEDGE -> {
202-
// CODE_REVIEW and DOCUMENT_READER have their own full-page interfaces
201+
AgentType.KNOWLEDGE,
202+
AgentType.CHAT_DB -> {
203+
// CODE_REVIEW, DOCUMENT_READER and CHAT_DB have their own full-page interfaces
203204
// They should not reach here - handled by AgentInterfaceRouter
204205
}
205206

mpp-ui/src/commonMain/kotlin/cc/unitmesh/devins/ui/compose/agent/AgentInterfaceRouter.kt

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
package cc.unitmesh.devins.ui.compose.agent
22

3+
import androidx.compose.foundation.layout.fillMaxSize
34
import androidx.compose.runtime.Composable
45
import androidx.compose.ui.Modifier
56
import cc.unitmesh.agent.AgentType
7+
import cc.unitmesh.devins.ui.compose.agent.chatdb.ChatDBPage
68
import cc.unitmesh.devins.ui.compose.agent.codereview.CodeReviewPage
79
import cc.unitmesh.devins.ui.remote.RemoteAgentChatInterface
10+
import cc.unitmesh.devins.workspace.Workspace
811
import cc.unitmesh.llm.KoogLLMService
912

1013
/**
@@ -53,9 +56,35 @@ fun AgentInterfaceRouter(
5356
onProjectChange: (String) -> Unit = {},
5457
onGitUrlChange: (String) -> Unit = {},
5558
onNotification: (String, String) -> Unit = { _, _ -> },
59+
workspace: Workspace? = null,
5660
modifier: Modifier = Modifier
5761
) {
5862
when (selectedAgentType) {
63+
AgentType.CHAT_DB -> {
64+
if (workspace != null) {
65+
ChatDBPage(
66+
workspace = workspace,
67+
llmService = llmService,
68+
modifier = modifier,
69+
onBack = {
70+
onAgentTypeChange(AgentType.CODING)
71+
},
72+
onNotification = onNotification
73+
)
74+
} else {
75+
// Show placeholder when workspace is not available
76+
androidx.compose.foundation.layout.Box(
77+
modifier = modifier.fillMaxSize(),
78+
contentAlignment = androidx.compose.ui.Alignment.Center
79+
) {
80+
androidx.compose.material3.Text(
81+
text = "Please select a workspace to use ChatDB",
82+
style = androidx.compose.material3.MaterialTheme.typography.bodyLarge
83+
)
84+
}
85+
}
86+
}
87+
5988
AgentType.KNOWLEDGE -> {
6089
cc.unitmesh.devins.ui.compose.document.DocumentReaderPage(
6190
modifier = modifier,
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
package cc.unitmesh.devins.ui.compose.agent.chatdb
2+
3+
import androidx.compose.foundation.layout.*
4+
import androidx.compose.material3.*
5+
import androidx.compose.runtime.*
6+
import androidx.compose.ui.Alignment
7+
import androidx.compose.ui.Modifier
8+
import androidx.compose.ui.unit.dp
9+
import cc.unitmesh.devins.ui.compose.agent.chatdb.components.*
10+
import cc.unitmesh.devins.ui.compose.agent.chatdb.model.ConnectionStatus
11+
import cc.unitmesh.devins.ui.compose.icons.AutoDevComposeIcons
12+
import cc.unitmesh.devins.workspace.Workspace
13+
import cc.unitmesh.llm.KoogLLMService
14+
import kotlinx.coroutines.flow.collectLatest
15+
16+
/**
17+
* ChatDB Page - Main page for text-to-SQL agent
18+
*
19+
* Left side: Data source management panel
20+
* Right side: Chat area for natural language to SQL queries
21+
*/
22+
@OptIn(ExperimentalMaterial3Api::class)
23+
@Composable
24+
fun ChatDBPage(
25+
workspace: Workspace,
26+
llmService: KoogLLMService?,
27+
modifier: Modifier = Modifier,
28+
onBack: () -> Unit,
29+
onNotification: (String, String) -> Unit = { _, _ -> }
30+
) {
31+
val viewModel = remember { ChatDBViewModel(workspace) }
32+
val state = viewModel.state
33+
34+
// Collect notifications
35+
LaunchedEffect(viewModel) {
36+
viewModel.notificationEvent.collectLatest { (title, message) ->
37+
onNotification(title, message)
38+
}
39+
}
40+
41+
// Cleanup on dispose
42+
DisposableEffect(viewModel) {
43+
onDispose {
44+
viewModel.dispose()
45+
}
46+
}
47+
48+
Scaffold(
49+
topBar = {
50+
TopAppBar(
51+
title = { Text("ChatDB") },
52+
navigationIcon = {
53+
IconButton(onClick = onBack) {
54+
Icon(AutoDevComposeIcons.ArrowBack, contentDescription = "Back")
55+
}
56+
},
57+
actions = {
58+
// Schema info button when connected
59+
if (state.connectionStatus is ConnectionStatus.Connected) {
60+
var showSchemaDialog by remember { mutableStateOf(false) }
61+
IconButton(onClick = { showSchemaDialog = true }) {
62+
Icon(AutoDevComposeIcons.Schema, contentDescription = "View Schema")
63+
}
64+
if (showSchemaDialog) {
65+
SchemaInfoDialog(
66+
schema = viewModel.getSchema(),
67+
onDismiss = { showSchemaDialog = false }
68+
)
69+
}
70+
}
71+
}
72+
)
73+
},
74+
modifier = modifier
75+
) { paddingValues ->
76+
Row(
77+
modifier = Modifier
78+
.fillMaxSize()
79+
.padding(paddingValues)
80+
) {
81+
// Left panel - Data source management
82+
DataSourcePanel(
83+
dataSources = state.filteredDataSources,
84+
selectedDataSourceId = state.selectedDataSourceId,
85+
connectionStatus = state.connectionStatus,
86+
filterQuery = state.filterQuery,
87+
onFilterChange = viewModel::setFilterQuery,
88+
onSelectDataSource = viewModel::selectDataSource,
89+
onAddClick = viewModel::openAddDialog,
90+
onEditClick = viewModel::openEditDialog,
91+
onDeleteClick = viewModel::deleteDataSource,
92+
onConnectClick = viewModel::connect,
93+
onDisconnectClick = viewModel::disconnect,
94+
modifier = Modifier.width(280.dp)
95+
)
96+
97+
VerticalDivider()
98+
99+
// Right panel - Chat area
100+
ChatDBChatPane(
101+
renderer = viewModel.renderer,
102+
connectionStatus = state.connectionStatus,
103+
schema = viewModel.getSchema(),
104+
isGenerating = viewModel.isGenerating,
105+
onSendMessage = viewModel::sendMessage,
106+
onStopGeneration = viewModel::stopGeneration,
107+
modifier = Modifier.weight(1f)
108+
)
109+
}
110+
111+
// Config dialog
112+
if (state.isConfigDialogOpen) {
113+
DataSourceConfigDialog(
114+
existingConfig = state.editingDataSource,
115+
onDismiss = viewModel::closeConfigDialog,
116+
onSave = { config ->
117+
if (state.editingDataSource != null) {
118+
viewModel.updateDataSource(config)
119+
} else {
120+
viewModel.addDataSource(config)
121+
}
122+
}
123+
)
124+
}
125+
}
126+
}
127+
128+
@Composable
129+
private fun SchemaInfoDialog(
130+
schema: cc.unitmesh.agent.database.DatabaseSchema?,
131+
onDismiss: () -> Unit
132+
) {
133+
AlertDialog(
134+
onDismissRequest = onDismiss,
135+
title = { Text("Database Schema") },
136+
text = {
137+
if (schema != null) {
138+
Column {
139+
Text(
140+
text = "${schema.tables.size} tables",
141+
style = MaterialTheme.typography.labelMedium
142+
)
143+
Spacer(modifier = Modifier.height(8.dp))
144+
schema.tables.take(10).forEach { table ->
145+
Text(
146+
text = "${table.name} (${table.columns.size} columns)",
147+
style = MaterialTheme.typography.bodySmall
148+
)
149+
}
150+
if (schema.tables.size > 10) {
151+
Text(
152+
text = "... and ${schema.tables.size - 10} more",
153+
style = MaterialTheme.typography.bodySmall,
154+
color = MaterialTheme.colorScheme.onSurfaceVariant
155+
)
156+
}
157+
}
158+
} else {
159+
Text("No schema available")
160+
}
161+
},
162+
confirmButton = {
163+
TextButton(onClick = onDismiss) {
164+
Text("Close")
165+
}
166+
}
167+
)
168+
}
169+

0 commit comments

Comments
 (0)