@@ -44,12 +44,16 @@ import androidx.compose.material.icons.filled.Memory
4444import androidx.compose.material.icons.filled.Notifications
4545import androidx.compose.material.icons.filled.PhotoCamera
4646import androidx.compose.material.icons.filled.ScreenshotMonitor
47+ import androidx.compose.material.icons.filled.AutoAwesome
48+ import androidx.compose.material3.AlertDialog
4749import androidx.compose.material3.HorizontalDivider
4850import androidx.compose.material3.Icon
4951import androidx.compose.material3.MaterialTheme
5052import androidx.compose.material3.Surface
5153import androidx.compose.material3.Text
54+ import androidx.compose.material3.TextButton
5255import androidx.compose.runtime.Composable
56+ import androidx.compose.runtime.LaunchedEffect
5357import androidx.compose.runtime.getValue
5458import androidx.compose.runtime.mutableStateOf
5559import androidx.compose.runtime.remember
@@ -67,6 +71,8 @@ import androidx.compose.ui.unit.dp
6771import androidx.compose.ui.window.Popup
6872import androidx.compose.ui.window.PopupProperties
6973import com.ai.assistance.operit.R
74+ import com.ai.assistance.operit.core.tools.skill.SkillPackage
75+ import com.ai.assistance.operit.data.skill.SkillRepository
7076import kotlinx.coroutines.launch
7177import java.io.File
7278import androidx.core.content.ContextCompat
@@ -83,6 +89,7 @@ fun AttachmentSelectorPanel(
8389 onAttachNotifications : () -> Unit = {},
8490 onAttachLocation : () -> Unit = {},
8591 onAttachMemory : () -> Unit = {},
92+ onAttachSkill : (String ) -> Unit = {},
8693 onTakePhoto : (Uri ) -> Unit ,
8794 userQuery : String = "",
8895 onDismiss : () -> Unit
@@ -94,6 +101,8 @@ fun AttachmentSelectorPanel(
94101 onDismiss = onDismiss,
95102 )
96103
104+ var showSkillDialog by remember { mutableStateOf(false ) }
105+
97106 // 文件/图片选择器启动器
98107 val imagePickerLauncher = rememberLauncherForActivityResult(
99108 contract = ActivityResultContracts .GetMultipleContents ()
@@ -212,15 +221,20 @@ fun AttachmentSelectorPanel(
212221 onAttachLocation()
213222 onDismiss()
214223 }
224+ ),
225+ AttachmentPanelItem (
226+ icon = Icons .Default .AutoAwesome ,
227+ label = context.getString(R .string.attachment_skill),
228+ onClick = { showSkillDialog = true }
215229 )
216230 )
217231
218- val pages = panelItems.chunked(8 ).ifEmpty { listOf (emptyList()) }
232+ val pages = panelItems.chunked(9 ).ifEmpty { listOf (emptyList()) }
219233 val pagerState = rememberPagerState(pageCount = { pages.size })
220234
221235 HorizontalPager (state = pagerState, modifier = Modifier .fillMaxWidth()) { pageIndex ->
222236 val pageItems = pages[pageIndex]
223- val paddedItems = pageItems + List (8 - pageItems.size) { null }
237+ val paddedItems = pageItems + List (9 - pageItems.size) { null }
224238
225239 Column (modifier = Modifier .fillMaxWidth()) {
226240 // 第一行选项
@@ -231,7 +245,7 @@ fun AttachmentSelectorPanel(
231245 horizontalArrangement = Arrangement .SpaceEvenly ,
232246 verticalAlignment = Alignment .CenterVertically
233247 ) {
234- paddedItems.take(4 ).forEach { item ->
248+ paddedItems.take(3 ).forEach { item ->
235249 if (item == null ) {
236250 AttachmentOptionPlaceholder ()
237251 } else {
@@ -254,7 +268,30 @@ fun AttachmentSelectorPanel(
254268 horizontalArrangement = Arrangement .SpaceEvenly ,
255269 verticalAlignment = Alignment .CenterVertically
256270 ) {
257- paddedItems.drop(4 ).take(4 ).forEach { item ->
271+ paddedItems.drop(3 ).take(3 ).forEach { item ->
272+ if (item == null ) {
273+ AttachmentOptionPlaceholder ()
274+ } else {
275+ AttachmentOption (
276+ icon = item.icon,
277+ label = item.label,
278+ onClick = item.onClick
279+ )
280+ }
281+ }
282+ }
283+
284+ Spacer (modifier = Modifier .height(16 .dp))
285+
286+ // 第三行选项
287+ Row (
288+ modifier =
289+ Modifier .fillMaxWidth().padding(horizontal = 16 .dp)
290+ .heightIn(min = 96 .dp),
291+ horizontalArrangement = Arrangement .SpaceEvenly ,
292+ verticalAlignment = Alignment .CenterVertically
293+ ) {
294+ paddedItems.drop(6 ).take(3 ).forEach { item ->
258295 if (item == null ) {
259296 AttachmentOptionPlaceholder ()
260297 } else {
@@ -298,9 +335,18 @@ fun AttachmentSelectorPanel(
298335 }
299336 }
300337 }
301- }
302338
303- /* * Agent 模式用的附件弹窗(上方悬浮样式,功能与经典模式 8 项保持一致) */
339+ // 技能选择对话框
340+ SkillSelectorDialog (
341+ visible = showSkillDialog,
342+ onDismiss = { showSkillDialog = false },
343+ onSkillSelected = { skillName ->
344+ onAttachSkill(skillName)
345+ showSkillDialog = false
346+ onDismiss()
347+ }
348+ )
349+ }
304350@Composable
305351fun AttachmentSelectorPopupPanel (
306352 visible : Boolean ,
@@ -311,6 +357,7 @@ fun AttachmentSelectorPopupPanel(
311357 onAttachNotifications : () -> Unit = {},
312358 onAttachLocation : () -> Unit = {},
313359 onAttachMemory : () -> Unit = {},
360+ onAttachSkill : (String ) -> Unit = {},
314361 onTakePhoto : (Uri ) -> Unit ,
315362 onDismiss : () -> Unit
316363) {
@@ -323,6 +370,8 @@ fun AttachmentSelectorPopupPanel(
323370 onDismiss = onDismiss,
324371 )
325372
373+ var showSkillDialog by remember { mutableStateOf(false ) }
374+
326375 val imagePickerLauncher = rememberLauncherForActivityResult(
327376 contract = ActivityResultContracts .GetMultipleContents ()
328377 ) { uris: List <Uri > ->
@@ -406,6 +455,11 @@ fun AttachmentSelectorPopupPanel(
406455 onAttachLocation()
407456 onDismiss()
408457 }
458+ ),
459+ AttachmentPanelItem (
460+ icon = Icons .Default .AutoAwesome ,
461+ label = context.getString(R .string.attachment_skill),
462+ onClick = { showSkillDialog = true }
409463 )
410464 )
411465
@@ -476,6 +530,17 @@ fun AttachmentSelectorPopupPanel(
476530 }
477531 }
478532 }
533+
534+ // 技能选择对话框
535+ SkillSelectorDialog (
536+ visible = showSkillDialog,
537+ onDismiss = { showSkillDialog = false },
538+ onSkillSelected = { skillName ->
539+ onAttachSkill(skillName)
540+ showSkillDialog = false
541+ onDismiss()
542+ }
543+ )
479544}
480545
481546private data class AttachmentPanelItem (
@@ -601,3 +666,94 @@ private fun AttachmentOption(icon: ImageVector, label: String, onClick: () -> Un
601666private fun AttachmentOptionPlaceholder () {
602667 Spacer (modifier = Modifier .width(70 .dp).padding(horizontal = 8 .dp, vertical = 8 .dp))
603668}
669+
670+ /* * 技能选择对话框 */
671+ @Composable
672+ fun SkillSelectorDialog (
673+ visible : Boolean ,
674+ onDismiss : () -> Unit ,
675+ onSkillSelected : (String ) -> Unit
676+ ) {
677+ if (! visible) return
678+
679+ val context = LocalContext .current
680+ val skillRepository = remember { SkillRepository .getInstance(context) }
681+ var skills by remember { mutableStateOf<Map <String , SkillPackage >>(emptyMap()) }
682+
683+ LaunchedEffect (visible) {
684+ if (visible) {
685+ skills = skillRepository.getAiVisibleSkillPackages()
686+ }
687+ }
688+
689+ AlertDialog (
690+ onDismissRequest = onDismiss,
691+ title = {
692+ Text (
693+ text = context.getString(R .string.attachment_skill_select_title),
694+ style = MaterialTheme .typography.titleMedium
695+ )
696+ },
697+ text = {
698+ if (skills.isEmpty()) {
699+ Box (
700+ modifier = Modifier .fillMaxWidth().padding(vertical = 24 .dp),
701+ contentAlignment = Alignment .Center
702+ ) {
703+ Text (
704+ text = context.getString(R .string.attachment_skill_empty),
705+ style = MaterialTheme .typography.bodyMedium,
706+ color = MaterialTheme .colorScheme.onSurfaceVariant
707+ )
708+ }
709+ } else {
710+ Column (
711+ modifier = Modifier .fillMaxWidth().heightIn(max = 400 .dp).verticalScroll(rememberScrollState())
712+ ) {
713+ skills.forEach { (skillName, skillPackage) ->
714+ Surface (
715+ modifier = Modifier .fillMaxWidth().padding(vertical = 2 .dp),
716+ shape = RoundedCornerShape (8 .dp),
717+ color = Color .Transparent ,
718+ onClick = { onSkillSelected(skillName) }
719+ ) {
720+ Row (
721+ modifier = Modifier .fillMaxWidth().padding(horizontal = 8 .dp, vertical = 10 .dp),
722+ verticalAlignment = Alignment .CenterVertically
723+ ) {
724+ Icon (
725+ imageVector = Icons .Default .AutoAwesome ,
726+ contentDescription = null ,
727+ tint = MaterialTheme .colorScheme.primary,
728+ modifier = Modifier .size(20 .dp)
729+ )
730+ Spacer (modifier = Modifier .width(12 .dp))
731+ Column (modifier = Modifier .weight(1f )) {
732+ Text (
733+ text = skillName,
734+ style = MaterialTheme .typography.bodyMedium,
735+ color = MaterialTheme .colorScheme.onSurface
736+ )
737+ if (skillPackage.description.isNotBlank()) {
738+ Text (
739+ text = skillPackage.description,
740+ style = MaterialTheme .typography.bodySmall,
741+ color = MaterialTheme .colorScheme.onSurfaceVariant,
742+ maxLines = 2 ,
743+ overflow = TextOverflow .Ellipsis
744+ )
745+ }
746+ }
747+ }
748+ }
749+ }
750+ }
751+ }
752+ },
753+ confirmButton = {
754+ TextButton (onClick = onDismiss) {
755+ Text (context.getString(R .string.cancel))
756+ }
757+ }
758+ )
759+ }
0 commit comments