Переглянути джерело

[feat]键盘插件,AI键盘,分开通用键盘面板和开场白面板

hezihao 8 місяців тому
батько
коміт
497f998ace

+ 4 - 1
plugins/keyboard_android/android/src/main/kotlin/com/atmob/keyboard_android/component/base/RouteComponent.kt

@@ -52,7 +52,10 @@ class RouteComponent @JvmOverloads constructor(
         if (targetComponent == null) {
             routeCallback?.onLost()
         } else {
-            // 找到目标组件,路由前,执行路由拦截器
+            // 找到目标组件
+            routeCallback?.onFound()
+
+            // 路由前,执行路由拦截器
             RouteInterceptorManager.executeInterceptors(targetComponent, object : InterceptorCallback {
                 override fun onContinue() {
                     // 拦截器放行,继续路由

+ 9 - 0
plugins/keyboard_android/android/src/main/kotlin/com/atmob/keyboard_android/component/child/IAiKeyboardCommonPanelComponent.kt

@@ -0,0 +1,9 @@
+package com.atmob.keyboard_android.component.child
+
+import com.atmob.keyboard_android.component.base.IUIComponent
+
+/**
+ * AI键盘-通用类型-面板接口
+ */
+interface IAiKeyboardCommonPanelComponent : IUIComponent {
+}

+ 9 - 0
plugins/keyboard_android/android/src/main/kotlin/com/atmob/keyboard_android/component/child/IAiKeyboardProloguePanelComponent.kt

@@ -0,0 +1,9 @@
+package com.atmob.keyboard_android.component.child
+
+import com.atmob.keyboard_android.component.base.IUIComponent
+
+/**
+ * AI键盘-开场白类型-面板接口
+ */
+interface IAiKeyboardProloguePanelComponent : IUIComponent {
+}

+ 246 - 0
plugins/keyboard_android/android/src/main/kotlin/com/atmob/keyboard_android/component/child/impl/AiKeyboardCommonPanelComponent.kt

@@ -0,0 +1,246 @@
+package com.atmob.keyboard_android.component.child.impl
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import androidx.recyclerview.widget.RecyclerView
+import com.atmob.keyboard_android.R
+import com.atmob.keyboard_android.component.base.BaseUIComponent
+import com.atmob.keyboard_android.component.base.FakeComponent
+import com.atmob.keyboard_android.component.base.RouteComponent
+import com.atmob.keyboard_android.component.child.IAiChatComponent
+import com.atmob.keyboard_android.component.child.IAiKeyboardCommonPanelComponent
+import com.atmob.keyboard_android.component.item.AiKeyboardKeyViewBinder
+import com.atmob.keyboard_android.constant.Constants
+import com.atmob.keyboard_android.enums.HelpMode
+import com.atmob.keyboard_android.ext.click
+import com.atmob.keyboard_android.ext.setGone
+import com.atmob.keyboard_android.ext.setVisible
+import com.atmob.keyboard_android.model.AiKeyboardKeyModel
+import com.atmob.keyboard_android.util.InputMethodUtil
+import com.atmob.keyboard_android.util.KeyboardHolder
+import com.atmob.keyboard_android.util.LogUtil
+import com.atmob.keyboard_android.util.UserInfoHelper
+import com.atmob.keyboard_android.util.recyclerview.GridDivider
+import com.atmob.keyboard_android.widget.LongTouchContainer
+import com.blankj.utilcode.util.ConvertUtils
+import com.blankj.utilcode.util.ToastUtils
+import com.gcssloop.widget.PagerGridLayoutManager
+import com.gcssloop.widget.PagerGridLayoutManager.PageListener
+import com.gcssloop.widget.PagerGridSnapHelper
+import me.drakeet.multitype.Items
+import me.drakeet.multitype.MultiTypeAdapter
+
+/**
+ * Ai键盘-通用类型-面板
+ */
+class AiKeyboardCommonPanelComponent @JvmOverloads constructor(
+    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
+) : BaseUIComponent<IAiKeyboardCommonPanelComponent>(context, attrs, defStyleAttr),
+    IAiKeyboardCommonPanelComponent {
+    private lateinit var vKeyboardKeyContent: View
+    private lateinit var vCommonRouteComponent: RouteComponent
+    private lateinit var vPasteBarLayout: View
+    private lateinit var vKeyList: RecyclerView
+    private lateinit var vPasteBtn: View
+    private lateinit var vDeleteBtn: LongTouchContainer
+    private lateinit var vClearBtn: View
+    private lateinit var vSendBtn: View
+
+    private lateinit var mKeyListItems: Items
+    private lateinit var mKeyListAdapter: MultiTypeAdapter
+
+    override fun onInflateViewId(): Int {
+        return R.layout.component_ai_keyboard_common_panel
+    }
+
+    override fun findView(view: View) {
+        vKeyboardKeyContent = view.findViewById(R.id.keyboard_key_content)
+        vCommonRouteComponent = view.findViewById(R.id.common_route_component)
+        vPasteBarLayout = view.findViewById(R.id.paste_bar_layout)
+        vKeyList = view.findViewById(R.id.key_list)
+        vPasteBtn = view.findViewById(R.id.paste_btn)
+        vDeleteBtn = view.findViewById(R.id.delete_btn)
+        vClearBtn = view.findViewById(R.id.clear_btn)
+        vSendBtn = view.findViewById(R.id.send_btn)
+    }
+
+    override fun bindView(view: View) {
+        setupKeyList()
+        setupActionBtn()
+        setupViewModel()
+        setData()
+    }
+
+    override fun getComponentInterfaceClazz(): Class<IAiKeyboardCommonPanelComponent> {
+        return IAiKeyboardCommonPanelComponent::class.java
+    }
+
+    /**
+     * 设置列表
+     */
+    private fun setupKeyList() {
+        vKeyList.apply {
+            mKeyListItems = Items()
+            mKeyListAdapter = MultiTypeAdapter(mKeyListItems).apply {
+                register(AiKeyboardKeyModel::class.java, AiKeyboardKeyViewBinder {
+                    // 点击键盘按键,打开AI生成内容面板
+                    if (it.isVip) {
+                        // 检查是否VIP
+                        UserInfoHelper.checkVip {
+                            controlAiChatPageShowing(true)
+                            LogUtil.d("Ai键盘,已经是VIP,打开Ai内容面板")
+                        }
+                    } else {
+                        // 不需要VIP,直接打开
+                        controlAiChatPageShowing(true)
+                        LogUtil.d("Ai键盘,不需要VIP,直接打开Ai内容面板")
+                    }
+                })
+            }
+            // 水平分页布局管理器
+            layoutManager = PagerGridLayoutManager(
+                Constants.AI_KEYBOARD_KEY_LIST_SPAN_COUNT,
+                Constants.AI_KEYBOARD_KEY_LIST_SPAN_COUNT,
+                PagerGridLayoutManager.HORIZONTAL
+            ).apply {
+                // 设置滚动监听
+                setPageListener(object : PageListener {
+                    override fun onPageSizeChanged(pageSize: Int) {
+                        // 当总页数确定时的回调
+                    }
+
+                    override fun onPageSelect(pageIndex: Int) {
+                        // 当页面被选中时的回调(从 0 开始)
+                    }
+                })
+            }
+            // 设置滚动辅助工具
+            val pageSnapHelper = PagerGridSnapHelper().apply {
+                // 设置滚动阀值
+                setFlingThreshold(minFlingVelocity)
+            }
+            pageSnapHelper.attachToRecyclerView(this)
+            // 设置适配器
+            adapter = mKeyListAdapter
+            // 添加分割线
+            addItemDecoration(
+                GridDivider(
+                    Constants.AI_KEYBOARD_KEY_LIST_SPAN_COUNT,
+                    ConvertUtils.dp2px(6f)
+                )
+            )
+        }
+    }
+
+    /**
+     * 设置操作按钮
+     */
+    @SuppressLint("ClickableViewAccessibility")
+    private fun setupActionBtn() {
+        // 粘贴
+        vPasteBtn.click {
+            val text = KeyboardHolder.getKeyboardService()
+                ?.getKeyboardViewModel()?.userClipboardData?.value ?: ""
+            if (text.isBlank()) {
+                return@click
+            }
+            KeyboardHolder.getKeyboardService()?.asInputMethodService()?.let {
+                InputMethodUtil.inputText(it.currentInputConnection, text)
+            }
+        }
+
+        // 删除
+        vDeleteBtn.setOnLongPressListener {
+            KeyboardHolder.getKeyboardService()?.asInputMethodService()?.let {
+                InputMethodUtil.deleteInput(it.currentInputConnection)
+            }
+        }
+
+        // 清空
+        vClearBtn.click {
+            KeyboardHolder.getKeyboardService()?.asInputMethodService()?.let {
+                InputMethodUtil.clearInput(it.currentInputConnection)
+            }
+        }
+
+        // 发送
+        vSendBtn.click {
+            KeyboardHolder.getKeyboardService()?.asInputMethodService()?.let {
+                InputMethodUtil.clickSendBtn(it.currentInputConnection)
+            }
+        }
+    }
+
+    /**
+     * 配置ViewModel
+     */
+    private fun setupViewModel() {
+        KeyboardHolder.getKeyboardService()?.run {
+            // 监听帮助模式更新,切换键盘
+            getKeyboardViewModel().helpMode.observe(getLifecycleOwner()) { newMode ->
+                // 隐藏AI生成内容面板
+                controlAiChatPageShowing(false)
+                // 重新加载按键列表
+                loadKeyListByHelpMode(newMode)
+            }
+            // 监听Ai内容生成页,是否显示
+            getKeyboardViewModel().aiChatPageShowing.observe(getLifecycleOwner()) { isShowing ->
+                // 显示Ai内容生成页,隐藏键盘列表
+                if (isShowing) {
+                    vKeyList.setGone()
+                } else {
+                    // Ai生成页关闭,则显示键盘列表
+                    vKeyList.setVisible()
+                }
+            }
+        }
+    }
+
+    /**
+     * 填充数据
+     */
+    @SuppressLint("NotifyDataSetChanged")
+    private fun setData() {
+    }
+
+    /**
+     * 根据选择的帮助模式,加载按键列表
+     */
+    @SuppressLint("NotifyDataSetChanged")
+    private fun loadKeyListByHelpMode(helpMode: HelpMode) {
+        // 非开场白类型
+        if (HelpMode.OPEN_REMARKS != helpMode) {
+            KeyboardHolder.getKeyboardService()?.getKeyboardViewModel()
+                ?.getCharacterList(onSuccess = {
+                    val newList = (it.characterInfos ?: listOf()).map {
+                        AiKeyboardKeyModel(
+                            // 显示的文本 = 表情 + 名字
+                            text = "${it.emoji} ${it.name}",
+                            // 是否需要VIP
+                            isVip = it.isVip
+                        )
+                    }
+                    mKeyListItems.clear()
+                    mKeyListItems.addAll(newList)
+                    mKeyListAdapter.notifyDataSetChanged()
+                }, onFail = {
+                    ToastUtils.showShort(it)
+                })
+        }
+    }
+
+    /**
+     * 控制Ai生成内容面板,是否显示
+     */
+    private fun controlAiChatPageShowing(isShow: Boolean) {
+        // 切换到Ai生成内容页
+        if (isShow) {
+            vCommonRouteComponent.routeChildComponent(IAiChatComponent::class.java)
+        } else {
+            // 切换回键盘页
+            vCommonRouteComponent.routeChildComponent(FakeComponent::class.java, true)
+        }
+    }
+}

+ 14 - 338
plugins/keyboard_android/android/src/main/kotlin/com/atmob/keyboard_android/component/child/impl/AiKeyboardComponent.kt

@@ -4,90 +4,36 @@ import android.annotation.SuppressLint
 import android.content.Context
 import android.util.AttributeSet
 import android.view.View
-import androidx.recyclerview.widget.RecyclerView
 import com.atmob.keyboard_android.R
 import com.atmob.keyboard_android.component.base.BaseUIComponent
-import com.atmob.keyboard_android.component.base.FakeComponent
 import com.atmob.keyboard_android.component.base.RouteComponent
-import com.atmob.keyboard_android.component.child.IAiChatComponent
 import com.atmob.keyboard_android.component.child.IAiKeyboardComponent
-import com.atmob.keyboard_android.component.item.AiKeyboardKeyViewBinder
-import com.atmob.keyboard_android.constant.Constants
 import com.atmob.keyboard_android.enums.HelpMode
 import com.atmob.keyboard_android.enums.KeyboardGlobalType
-import com.atmob.keyboard_android.ext.click
-import com.atmob.keyboard_android.ext.setGone
-import com.atmob.keyboard_android.ext.setVisible
-import com.atmob.keyboard_android.model.AiKeyboardKeyModel
-import com.atmob.keyboard_android.util.InputMethodUtil
 import com.atmob.keyboard_android.util.KeyboardHolder
-import com.atmob.keyboard_android.util.LogUtil
-import com.atmob.keyboard_android.util.UserInfoHelper
-import com.atmob.keyboard_android.util.recyclerview.GridDivider
-import com.atmob.keyboard_android.widget.LongTouchContainer
-import com.atmob.keyboard_android.widget.indicator.TabPagerTitleView
-import com.blankj.utilcode.util.ConvertUtils
-import com.blankj.utilcode.util.ToastUtils
-import com.gcssloop.widget.PagerGridLayoutManager
-import com.gcssloop.widget.PagerGridLayoutManager.PageListener
-import com.gcssloop.widget.PagerGridSnapHelper
-import me.drakeet.multitype.Items
-import me.drakeet.multitype.MultiTypeAdapter
-import net.lucode.hackware.magicindicator.MagicIndicator
-import net.lucode.hackware.magicindicator.buildins.UIUtil
-import net.lucode.hackware.magicindicator.buildins.commonnavigator.CommonNavigator
-import net.lucode.hackware.magicindicator.buildins.commonnavigator.abs.CommonNavigatorAdapter
-import net.lucode.hackware.magicindicator.buildins.commonnavigator.abs.IPagerIndicator
-import net.lucode.hackware.magicindicator.buildins.commonnavigator.abs.IPagerTitleView
-import net.lucode.hackware.magicindicator.buildins.commonnavigator.indicators.LinePagerIndicator
 
 
 /**
- * AI键盘组件
+ * AI键盘组件,里面包含2个组件,分别为通用类型键盘面板、开场白类型键盘面板
  */
 class AiKeyboardComponent @JvmOverloads constructor(
     context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
 ) : BaseUIComponent<IAiKeyboardComponent>(context, attrs, defStyleAttr), IAiKeyboardComponent {
-    private lateinit var vTabBarLayout: View
-    private lateinit var vMagicIndicator: MagicIndicator
-    private lateinit var vKeyboardKeyContent: View
-    private lateinit var vRouteComponent: RouteComponent
-    private lateinit var vPasteBarLayout: View
-    private lateinit var vKeyList: RecyclerView
-    private lateinit var vPasteBtn: View
-    private lateinit var vDeleteBtn: LongTouchContainer
-    private lateinit var vClearBtn: View
-    private lateinit var vSendBtn: View
-
-    private lateinit var mKeyListItems: Items
-    private lateinit var mKeyListAdapter: MultiTypeAdapter
-
-    /**
-     * Tab列表
-     */
-    private lateinit var mTabList: MutableList<String>
+    private lateinit var vAiKeyboardRouteComponent: RouteComponent
+    private lateinit var vCommonPanel: AiKeyboardCommonPanelComponent
+    private lateinit var vProloguePanel: AiKeyboardProloguePanelComponent
 
     override fun onInflateViewId(): Int {
         return R.layout.component_ai_keyboard
     }
 
     override fun findView(view: View) {
-        vTabBarLayout = view.findViewById(R.id.tar_bar_layout)
-        vMagicIndicator = view.findViewById(R.id.magic_indicator)
-        vKeyboardKeyContent = view.findViewById(R.id.keyboard_key_content)
-        vRouteComponent = view.findViewById(R.id.route_component)
-        vPasteBarLayout = view.findViewById(R.id.paste_bar_layout)
-        vKeyList = view.findViewById(R.id.key_list)
-        vPasteBtn = view.findViewById(R.id.paste_btn)
-        vDeleteBtn = view.findViewById(R.id.delete_btn)
-        vClearBtn = view.findViewById(R.id.clear_btn)
-        vSendBtn = view.findViewById(R.id.send_btn)
+        vAiKeyboardRouteComponent = view.findViewById(R.id.ai_keyboard_route_component)
+        vCommonPanel = view.findViewById(R.id.common_panel)
+        vProloguePanel = view.findViewById(R.id.prologue_panel)
     }
 
     override fun bindView(view: View) {
-        setupTabBar()
-        setupKeyList()
-        setupActionBtn()
         setupViewModel()
         setData()
     }
@@ -97,188 +43,11 @@ class AiKeyboardComponent @JvmOverloads constructor(
     }
 
     /**
-     * 配置Tab栏
-     */
-    private fun setupTabBar() {
-        // 初始化Tab列表
-        mTabList = mutableListOf()
-
-        // 配置指示器
-        vMagicIndicator.setNavigator(CommonNavigator(context).apply {
-            scrollPivotX = 0.35f
-            // 设置适配器
-            setAdapter(object : CommonNavigatorAdapter() {
-                override fun getCount(): Int {
-                    return mTabList.size
-                }
-
-                override fun getTitleView(context: Context, index: Int): IPagerTitleView {
-                    val titleView = object : TabPagerTitleView(context) {
-                        override fun onSelected(index: Int, totalCount: Int) {
-                            super.onSelected(index, totalCount)
-                            // 选中Tab,设置为粗体
-                            setBoldStyle(true)
-                        }
-
-                        override fun onDeselected(index: Int, totalCount: Int) {
-                            super.onDeselected(index, totalCount)
-                            // 不选中Tab,设置为普通体
-                            setBoldStyle(false)
-                        }
-                    }
-                    // Tab文字
-                    titleView.setText(mTabList[index])
-                    // Tab未选中时的字体颜色
-                    titleView.setTextColor(context.resources.getColor(R.color.text_color_secondary))
-                    // Tab选中时的字体颜色
-                    titleView.setClipColor(context.resources.getColor(R.color.text_color_primary))
-                    // 字体大小
-                    titleView.textSize = ConvertUtils.dp2px(12f).toFloat()
-                    // 设置padding,让文字距离背景有一定间距
-                    val paddingHorizontal = ConvertUtils.dp2px(18f)
-                    titleView.setPadding(paddingHorizontal, 0, paddingHorizontal, 0)
-                    // 点击Tab,切换页面
-                    titleView.setOnClickListener(object : OnClickListener {
-                        override fun onClick(v: View?) {
-                            // 更新Tab
-                            val newTab = mTabList[index]
-                            KeyboardHolder.getKeyboardService()?.getKeyboardViewModel()
-                                ?.updateCurrentPrologueTab(newTab)
-                        }
-                    })
-                    return titleView
-                }
-
-                override fun getIndicator(context: Context): IPagerIndicator {
-                    val indicator = LinePagerIndicator(context)
-                    val navigatorHeight =
-                        context.resources.getDimension(R.dimen.common_navigator_height)
-                    val borderWidth = UIUtil.dip2px(context, 1.0).toFloat()
-                    val lineHeight = navigatorHeight - 2 * borderWidth
-                    indicator.lineHeight = lineHeight
-                    indicator.roundRadius = lineHeight / 2
-                    indicator.yOffset = borderWidth
-                    // 指示器的背景颜色
-                    indicator.setColors(context.resources.getColor(R.color.bg_tab_indicator_selected))
-                    return indicator
-                }
-            })
-        })
-    }
-
-    /**
-     * 设置列表
-     */
-    private fun setupKeyList() {
-        vKeyList.apply {
-            mKeyListItems = Items()
-            mKeyListAdapter = MultiTypeAdapter(mKeyListItems).apply {
-                register(AiKeyboardKeyModel::class.java, AiKeyboardKeyViewBinder {
-                    // 点击键盘按键,打开AI生成内容面板
-                    if (it.isVip) {
-                        // 检查是否VIP
-                        UserInfoHelper.checkVip {
-                            controlAiChatPageShowing(true)
-                            LogUtil.d("Ai键盘,已经是VIP,打开Ai内容面板")
-                        }
-                    } else {
-                        // 不需要VIP,直接打开
-                        controlAiChatPageShowing(true)
-                        LogUtil.d("Ai键盘,不需要VIP,直接打开Ai内容面板")
-                    }
-                })
-            }
-            // 水平分页布局管理器
-            layoutManager = PagerGridLayoutManager(
-                Constants.AI_KEYBOARD_KEY_LIST_SPAN_COUNT,
-                Constants.AI_KEYBOARD_KEY_LIST_SPAN_COUNT,
-                PagerGridLayoutManager.HORIZONTAL
-            ).apply {
-                // 设置滚动监听
-                setPageListener(object : PageListener {
-                    override fun onPageSizeChanged(pageSize: Int) {
-                        // 当总页数确定时的回调
-                    }
-
-                    override fun onPageSelect(pageIndex: Int) {
-                        // 当页面被选中时的回调(从 0 开始)
-                    }
-                })
-            }
-            // 设置滚动辅助工具
-            val pageSnapHelper = PagerGridSnapHelper().apply {
-                // 设置滚动阀值
-                setFlingThreshold(minFlingVelocity)
-            }
-            pageSnapHelper.attachToRecyclerView(this)
-            // 设置适配器
-            adapter = mKeyListAdapter
-            // 添加分割线
-            addItemDecoration(
-                GridDivider(
-                    Constants.AI_KEYBOARD_KEY_LIST_SPAN_COUNT,
-                    ConvertUtils.dp2px(6f)
-                )
-            )
-        }
-    }
-
-    /**
-     * 设置操作按钮
-     */
-    @SuppressLint("ClickableViewAccessibility")
-    private fun setupActionBtn() {
-        // 粘贴
-        vPasteBtn.click {
-            val text = KeyboardHolder.getKeyboardService()
-                ?.getKeyboardViewModel()?.userClipboardData?.value ?: ""
-            if (text.isBlank()) {
-                return@click
-            }
-            KeyboardHolder.getKeyboardService()?.asInputMethodService()?.let {
-                InputMethodUtil.inputText(it.currentInputConnection, text)
-            }
-        }
-
-        // 删除
-        vDeleteBtn.setOnLongPressListener {
-            KeyboardHolder.getKeyboardService()?.asInputMethodService()?.let {
-                InputMethodUtil.deleteInput(it.currentInputConnection)
-            }
-        }
-
-        // 清空
-        vClearBtn.click {
-            KeyboardHolder.getKeyboardService()?.asInputMethodService()?.let {
-                InputMethodUtil.clearInput(it.currentInputConnection)
-            }
-        }
-
-        // 发送
-        vSendBtn.click {
-            KeyboardHolder.getKeyboardService()?.asInputMethodService()?.let {
-                InputMethodUtil.clickSendBtn(it.currentInputConnection)
-            }
-        }
-    }
-
-    /**
      * 配置ViewModel
      */
     private fun setupViewModel() {
         KeyboardHolder.getKeyboardService()?.run {
-            // 监听开场白列表更新
-            getKeyboardViewModel().prologueList.observe(getLifecycleOwner()) { list ->
-                // 更新Tab列表
-                mTabList.clear()
-                mTabList.addAll(list.map {
-                    it.title
-                }.toList())
-                // 刷新Tab栏
-                vMagicIndicator.navigator.notifyDataSetChanged()
-                LogUtil.d("开场白列表更新,刷新Tab栏...")
-            }
-            // 监听键盘类型切换
+            // 监听全局键盘类型切换
             getKeyboardViewModel().keyboardGlobalType.observe(getLifecycleOwner()) { globalKeyboardType ->
                 if (KeyboardGlobalType.AI_KEYBOARD == globalKeyboardType) {
                     show()
@@ -291,25 +60,6 @@ class AiKeyboardComponent @JvmOverloads constructor(
                 // 切换键盘
                 updateKeyboardByHelpMode(newMode)
             }
-            // 监听Tab切换,更新键盘列表
-            getKeyboardViewModel().currentPrologueTab.observe(getLifecycleOwner()) { newTab ->
-                // 切换Tab
-                vMagicIndicator.onPageSelected(mTabList.indexOf(newTab))
-                // 关闭Ai内容生成面板
-                controlAiChatPageShowing(false)
-                // 根据Tab,重新加载数据
-                loadKeyListByTab(newTab)
-            }
-            // 监听Ai内容生成页,是否显示
-            getKeyboardViewModel().aiChatPageShowing.observe(getLifecycleOwner()) { isShowing ->
-                // 显示Ai内容生成页,隐藏键盘列表
-                if (isShowing) {
-                    vKeyList.setGone()
-                } else {
-                    // Ai生成页关闭,则显示键盘列表
-                    vKeyList.setVisible()
-                }
-            }
         }
     }
 
@@ -318,63 +68,6 @@ class AiKeyboardComponent @JvmOverloads constructor(
      */
     @SuppressLint("NotifyDataSetChanged")
     private fun setData() {
-        KeyboardHolder.getKeyboardService()?.getKeyboardViewModel()?.getPrologueList(onFail = {
-            ToastUtils.showShort(it)
-        })
-    }
-
-    /**
-     * 根据选择的帮助模式,加载按键列表
-     */
-    @SuppressLint("NotifyDataSetChanged")
-    private fun loadKeyListByHelpMode(helpMode: HelpMode) {
-        // 开场白
-        if (HelpMode.OPEN_REMARKS == helpMode) {
-            val currentTab = KeyboardHolder.getKeyboardService()
-                ?.getKeyboardViewModel()?.currentPrologueTab?.value ?: ""
-            if (currentTab.isBlank()) {
-                return
-            }
-            loadKeyListByTab(currentTab)
-        } else {
-            // 其他类型
-            KeyboardHolder.getKeyboardService()?.getKeyboardViewModel()
-                ?.getCharacterList(onSuccess = {
-                    val newList = (it.characterInfos ?: listOf()).map {
-                        AiKeyboardKeyModel(
-                            // 显示的文本 = 表情 + 名字
-                            text = "${it.emoji} ${it.name}",
-                            // 是否需要VIP
-                            isVip = it.isVip
-                        )
-                    }
-                    mKeyListItems.clear()
-                    mKeyListItems.addAll(newList)
-                    mKeyListAdapter.notifyDataSetChanged()
-                }, onFail = {
-                    ToastUtils.showShort(it)
-                })
-        }
-    }
-
-    /**
-     * 根据Tab,加载按键列表
-     */
-    @SuppressLint("NotifyDataSetChanged")
-    private fun loadKeyListByTab(tab: String) {
-        val list = KeyboardHolder.getKeyboardService()?.getKeyboardViewModel()
-            ?.filterPrologueListByTab(tab) ?: listOf()
-        val newList = list.map {
-            AiKeyboardKeyModel(
-                // 显示的文本 = 名字
-                text = it.name,
-                // 是否需要VIP
-                isVip = false
-            )
-        }.toList()
-        mKeyListItems.clear()
-        mKeyListItems.addAll(newList)
-        mKeyListAdapter.notifyDataSetChanged()
     }
 
     /**
@@ -383,30 +76,13 @@ class AiKeyboardComponent @JvmOverloads constructor(
     private fun updateKeyboardByHelpMode(helpMode: HelpMode) {
         // 根据不同的模式,切换不同的键盘
         if (HelpMode.OPEN_REMARKS == helpMode) {
-            // 开场白,显示Tab布局,隐藏粘贴栏
-            vTabBarLayout.setVisible()
-            vPasteBarLayout.setGone()
-        } else {
-            // 其他模式,隐藏Tab布局,显示粘贴栏
-            vTabBarLayout.setGone()
-            vPasteBarLayout.setVisible()
-        }
-        // 隐藏AI生成内容面板
-        controlAiChatPageShowing(false)
-        // 重新加载按键列表
-        loadKeyListByHelpMode(helpMode)
-    }
-
-    /**
-     * 控制Ai生成内容面板,是否显示
-     */
-    private fun controlAiChatPageShowing(isShow: Boolean) {
-        // 切换到Ai生成内容页
-        if (isShow) {
-            vRouteComponent.routeChildComponent(IAiChatComponent::class.java)
+            // 开场白模式
+            vCommonPanel.hide()
+            vProloguePanel.show()
         } else {
-            // 切换回键盘页
-            vRouteComponent.routeChildComponent(FakeComponent::class.java, true)
+            // 其他模式
+            vCommonPanel.show()
+            vProloguePanel.hide()
         }
     }
 }

+ 331 - 0
plugins/keyboard_android/android/src/main/kotlin/com/atmob/keyboard_android/component/child/impl/AiKeyboardProloguePanelComponent.kt

@@ -0,0 +1,331 @@
+package com.atmob.keyboard_android.component.child.impl
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import androidx.recyclerview.widget.RecyclerView
+import com.atmob.keyboard_android.R
+import com.atmob.keyboard_android.component.base.BaseUIComponent
+import com.atmob.keyboard_android.component.base.FakeComponent
+import com.atmob.keyboard_android.component.base.RouteComponent
+import com.atmob.keyboard_android.component.child.IAiChatComponent
+import com.atmob.keyboard_android.component.child.IAiKeyboardProloguePanelComponent
+import com.atmob.keyboard_android.component.item.AiKeyboardKeyViewBinder
+import com.atmob.keyboard_android.constant.Constants
+import com.atmob.keyboard_android.ext.click
+import com.atmob.keyboard_android.ext.setGone
+import com.atmob.keyboard_android.ext.setVisible
+import com.atmob.keyboard_android.model.AiKeyboardKeyModel
+import com.atmob.keyboard_android.util.InputMethodUtil
+import com.atmob.keyboard_android.util.KeyboardHolder
+import com.atmob.keyboard_android.util.LogUtil
+import com.atmob.keyboard_android.util.UserInfoHelper
+import com.atmob.keyboard_android.util.recyclerview.GridDivider
+import com.atmob.keyboard_android.widget.LongTouchContainer
+import com.atmob.keyboard_android.widget.indicator.TabPagerTitleView
+import com.blankj.utilcode.util.ConvertUtils
+import com.blankj.utilcode.util.ToastUtils
+import com.gcssloop.widget.PagerGridLayoutManager
+import com.gcssloop.widget.PagerGridLayoutManager.PageListener
+import com.gcssloop.widget.PagerGridSnapHelper
+import me.drakeet.multitype.Items
+import me.drakeet.multitype.MultiTypeAdapter
+import net.lucode.hackware.magicindicator.MagicIndicator
+import net.lucode.hackware.magicindicator.buildins.UIUtil
+import net.lucode.hackware.magicindicator.buildins.commonnavigator.CommonNavigator
+import net.lucode.hackware.magicindicator.buildins.commonnavigator.abs.CommonNavigatorAdapter
+import net.lucode.hackware.magicindicator.buildins.commonnavigator.abs.IPagerIndicator
+import net.lucode.hackware.magicindicator.buildins.commonnavigator.abs.IPagerTitleView
+import net.lucode.hackware.magicindicator.buildins.commonnavigator.indicators.LinePagerIndicator
+
+/**
+ * Ai键盘-开场白类型-面板
+ */
+class AiKeyboardProloguePanelComponent @JvmOverloads constructor(
+    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
+) : BaseUIComponent<IAiKeyboardProloguePanelComponent>(context, attrs, defStyleAttr),
+    IAiKeyboardProloguePanelComponent {
+    private lateinit var vTabBarLayout: View
+    private lateinit var vMagicIndicator: MagicIndicator
+    private lateinit var vKeyboardKeyContent: View
+    private lateinit var vPrologueRouteComponent: RouteComponent
+    private lateinit var vKeyList: RecyclerView
+    private lateinit var vDeleteBtn: LongTouchContainer
+    private lateinit var vClearBtn: View
+    private lateinit var vSendBtn: View
+
+    private lateinit var mKeyListItems: Items
+    private lateinit var mKeyListAdapter: MultiTypeAdapter
+
+    /**
+     * Tab列表
+     */
+    private lateinit var mTabList: MutableList<String>
+
+    override fun onInflateViewId(): Int {
+        return R.layout.component_ai_keyboard_prologue_panel
+    }
+
+    override fun findView(view: View) {
+        vTabBarLayout = view.findViewById(R.id.tar_bar_layout)
+        vMagicIndicator = view.findViewById(R.id.magic_indicator)
+        vKeyboardKeyContent = view.findViewById(R.id.keyboard_key_content)
+        vPrologueRouteComponent = view.findViewById(R.id.prologue_route_component)
+        vKeyList = view.findViewById(R.id.key_list)
+        vDeleteBtn = view.findViewById(R.id.delete_btn)
+        vClearBtn = view.findViewById(R.id.clear_btn)
+        vSendBtn = view.findViewById(R.id.send_btn)
+    }
+
+    override fun bindView(view: View) {
+        setupTabBar()
+        setupKeyList()
+        setupActionBtn()
+        setupViewModel()
+        setData()
+    }
+
+    override fun getComponentInterfaceClazz(): Class<IAiKeyboardProloguePanelComponent> {
+        return IAiKeyboardProloguePanelComponent::class.java
+    }
+
+    /**
+     * 配置Tab栏
+     */
+    private fun setupTabBar() {
+        // 初始化Tab列表
+        mTabList = mutableListOf()
+
+        // 配置指示器
+        vMagicIndicator.setNavigator(CommonNavigator(context).apply {
+            scrollPivotX = 0.35f
+            // 设置适配器
+            setAdapter(object : CommonNavigatorAdapter() {
+                override fun getCount(): Int {
+                    return mTabList.size
+                }
+
+                override fun getTitleView(context: Context, index: Int): IPagerTitleView {
+                    val titleView = object : TabPagerTitleView(context) {
+                        override fun onSelected(index: Int, totalCount: Int) {
+                            super.onSelected(index, totalCount)
+                            // 选中Tab,设置为粗体
+                            setBoldStyle(true)
+                        }
+
+                        override fun onDeselected(index: Int, totalCount: Int) {
+                            super.onDeselected(index, totalCount)
+                            // 不选中Tab,设置为普通体
+                            setBoldStyle(false)
+                        }
+                    }
+                    // Tab文字
+                    titleView.setText(mTabList[index])
+                    // Tab未选中时的字体颜色
+                    titleView.setTextColor(context.resources.getColor(R.color.text_color_secondary))
+                    // Tab选中时的字体颜色
+                    titleView.setClipColor(context.resources.getColor(R.color.text_color_primary))
+                    // 字体大小
+                    titleView.textSize = ConvertUtils.dp2px(12f).toFloat()
+                    // 设置padding,让文字距离背景有一定间距
+                    val paddingHorizontal = ConvertUtils.dp2px(18f)
+                    titleView.setPadding(paddingHorizontal, 0, paddingHorizontal, 0)
+                    // 点击Tab,切换页面
+                    titleView.setOnClickListener(object : OnClickListener {
+                        override fun onClick(v: View?) {
+                            // 更新Tab
+                            val newTab = mTabList[index]
+                            KeyboardHolder.getKeyboardService()?.getKeyboardViewModel()
+                                ?.updateCurrentPrologueTab(newTab)
+                        }
+                    })
+                    return titleView
+                }
+
+                override fun getIndicator(context: Context): IPagerIndicator {
+                    val indicator = LinePagerIndicator(context)
+                    val navigatorHeight =
+                        context.resources.getDimension(R.dimen.common_navigator_height)
+                    val borderWidth = UIUtil.dip2px(context, 1.0).toFloat()
+                    val lineHeight = navigatorHeight - 2 * borderWidth
+                    indicator.lineHeight = lineHeight
+                    indicator.roundRadius = lineHeight / 2
+                    indicator.yOffset = borderWidth
+                    // 指示器的背景颜色
+                    indicator.setColors(context.resources.getColor(R.color.bg_tab_indicator_selected))
+                    return indicator
+                }
+            })
+        })
+    }
+
+    /**
+     * 设置列表
+     */
+    private fun setupKeyList() {
+        vKeyList.apply {
+            mKeyListItems = Items()
+            mKeyListAdapter = MultiTypeAdapter(mKeyListItems).apply {
+                register(AiKeyboardKeyModel::class.java, AiKeyboardKeyViewBinder {
+                    // 点击键盘按键,打开AI生成内容面板
+                    if (it.isVip) {
+                        // 检查是否VIP
+                        UserInfoHelper.checkVip {
+                            controlAiChatPageShowing(true)
+                            LogUtil.d("Ai键盘 => 开场白类型-键盘面板,已经是VIP,打开Ai内容面板")
+                        }
+                    } else {
+                        // 不需要VIP,直接打开
+                        controlAiChatPageShowing(true)
+                        LogUtil.d("Ai键盘 => 开场白类型-键盘面板,不需要VIP,直接打开Ai内容面板")
+                    }
+                })
+            }
+            // 水平分页布局管理器
+            layoutManager = PagerGridLayoutManager(
+                Constants.AI_KEYBOARD_KEY_LIST_SPAN_COUNT,
+                Constants.AI_KEYBOARD_KEY_LIST_SPAN_COUNT,
+                PagerGridLayoutManager.HORIZONTAL
+            ).apply {
+                // 设置滚动监听
+                setPageListener(object : PageListener {
+                    override fun onPageSizeChanged(pageSize: Int) {
+                        // 当总页数确定时的回调
+                    }
+
+                    override fun onPageSelect(pageIndex: Int) {
+                        // 当页面被选中时的回调(从 0 开始)
+                    }
+                })
+            }
+            // 设置滚动辅助工具
+            val pageSnapHelper = PagerGridSnapHelper().apply {
+                // 设置滚动阀值
+                setFlingThreshold(minFlingVelocity)
+            }
+            pageSnapHelper.attachToRecyclerView(this)
+            // 设置适配器
+            adapter = mKeyListAdapter
+            // 添加分割线
+            addItemDecoration(
+                GridDivider(
+                    Constants.AI_KEYBOARD_KEY_LIST_SPAN_COUNT,
+                    ConvertUtils.dp2px(6f)
+                )
+            )
+        }
+    }
+
+    /**
+     * 设置操作按钮
+     */
+    @SuppressLint("ClickableViewAccessibility")
+    private fun setupActionBtn() {
+        // 删除
+        vDeleteBtn.setOnLongPressListener {
+            KeyboardHolder.getKeyboardService()?.asInputMethodService()?.let {
+                InputMethodUtil.deleteInput(it.currentInputConnection)
+            }
+        }
+
+        // 清空
+        vClearBtn.click {
+            KeyboardHolder.getKeyboardService()?.asInputMethodService()?.let {
+                InputMethodUtil.clearInput(it.currentInputConnection)
+            }
+        }
+
+        // 发送
+        vSendBtn.click {
+            KeyboardHolder.getKeyboardService()?.asInputMethodService()?.let {
+                InputMethodUtil.clickSendBtn(it.currentInputConnection)
+            }
+        }
+    }
+
+    /**
+     * 配置ViewModel
+     */
+    private fun setupViewModel() {
+        KeyboardHolder.getKeyboardService()?.run {
+            // 监听开场白列表更新
+            getKeyboardViewModel().prologueList.observe(getLifecycleOwner()) { list ->
+                // 更新Tab列表
+                val tabList = list.map {
+                    it.title
+                }.toList()
+                mTabList.clear()
+                mTabList.addAll(tabList)
+                // 刷新Tab栏
+                vMagicIndicator.navigator.notifyDataSetChanged()
+                LogUtil.d("开场白列表更新,刷新Tab栏 => ${tabList.size}个Tab")
+                // 默认选中第一个Tab
+                vMagicIndicator.onPageSelected(0)
+            }
+            // 监听Tab切换,更新键盘列表
+            getKeyboardViewModel().currentPrologueTab.observe(getLifecycleOwner()) { newTab ->
+                // 切换Tab
+                vMagicIndicator.onPageSelected(mTabList.indexOf(newTab))
+                // 关闭Ai内容生成面板
+                controlAiChatPageShowing(false)
+                // 根据Tab,重新加载数据
+                loadKeyListByTab(newTab)
+            }
+            // 监听Ai内容生成页,是否显示
+            getKeyboardViewModel().aiChatPageShowing.observe(getLifecycleOwner()) { isShowing ->
+                // 显示Ai内容生成页,隐藏键盘列表
+                if (isShowing) {
+                    vKeyList.setGone()
+                } else {
+                    // Ai生成页关闭,则显示键盘列表
+                    vKeyList.setVisible()
+                }
+            }
+        }
+    }
+
+    /**
+     * 填充数据
+     */
+    @SuppressLint("NotifyDataSetChanged")
+    private fun setData() {
+        // 拉取开场白列表
+        KeyboardHolder.getKeyboardService()?.getKeyboardViewModel()?.getPrologueList(onFail = {
+            ToastUtils.showShort(it)
+        })
+    }
+
+    /**
+     * 根据Tab,加载按键列表
+     */
+    @SuppressLint("NotifyDataSetChanged")
+    private fun loadKeyListByTab(tab: String) {
+        val list = KeyboardHolder.getKeyboardService()?.getKeyboardViewModel()
+            ?.filterPrologueListByTab(tab) ?: listOf()
+        val newList = list.map {
+            AiKeyboardKeyModel(
+                // 显示的文本 = 名字
+                text = it.name,
+                // 是否需要VIP
+                isVip = false
+            )
+        }.toList()
+        mKeyListItems.clear()
+        mKeyListItems.addAll(newList)
+        mKeyListAdapter.notifyDataSetChanged()
+    }
+
+    /**
+     * 控制Ai生成内容面板,是否显示
+     */
+    private fun controlAiChatPageShowing(isShow: Boolean) {
+        // 切换到Ai生成内容页
+        if (isShow) {
+            vPrologueRouteComponent.routeChildComponent(IAiChatComponent::class.java)
+        } else {
+            // 切换回键盘页
+            vPrologueRouteComponent.routeChildComponent(FakeComponent::class.java, true)
+        }
+    }
+}

+ 12 - 108
plugins/keyboard_android/android/src/main/res/layout/component_ai_keyboard.xml

@@ -17,119 +17,23 @@
         android:visibility="gone"
         tools:visibility="visible" />
 
-    <FrameLayout
-        android:id="@+id/tar_bar_layout"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center_horizontal"
-        android:layout_marginTop="14dp"
-        android:layout_marginBottom="14dp"
-        android:background="@drawable/bg_tab_indicator"
-        android:paddingStart="2.5dp"
-        android:paddingTop="2dp"
-        android:paddingEnd="2.5dp"
-        android:paddingBottom="2dp"
-        android:visibility="gone"
-        tools:visibility="visible">
-
-        <net.lucode.hackware.magicindicator.MagicIndicator
-            android:id="@+id/magic_indicator"
-            android:layout_width="wrap_content"
-            android:layout_height="@dimen/common_navigator_height"
-            android:layout_gravity="center" />
-    </FrameLayout>
-
-    <LinearLayout
-        android:id="@+id/keyboard_key_content"
+    <com.atmob.keyboard_android.component.base.RouteComponent
+        android:id="@+id/ai_keyboard_route_component"
         android:layout_width="match_parent"
         android:layout_height="0dp"
-        android:layout_weight="1"
-        android:orientation="vertical">
+        android:layout_weight="1">
 
-        <LinearLayout
-            android:id="@+id/paste_bar_layout"
+        <!-- 通用类型-键盘面板 -->
+        <com.atmob.keyboard_android.component.child.impl.AiKeyboardCommonPanelComponent
+            android:id="@+id/common_panel"
             android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginBottom="6dp"
-            android:orientation="horizontal">
-
-            <com.atmob.keyboard_android.component.child.impl.PasteBarComponent
-                android:layout_width="0dp"
-                android:layout_height="wrap_content"
-                android:layout_gravity="center_vertical"
-                android:layout_marginStart="10dp"
-                android:layout_weight="1"
-                tools:background="@android:color/holo_red_dark"
-                tools:layout_height="46dp" />
+            android:layout_height="wrap_content" />
 
-            <TextView
-                android:id="@+id/paste_btn"
-                style="@style/keyboard_action_btn2"
-                android:layout_marginStart="10dp"
-                android:layout_marginTop="6dp"
-                android:layout_marginEnd="10dp"
-                android:text="@string/paste" />
-        </LinearLayout>
-
-        <LinearLayout
+        <!-- 开场白类型-键盘面板 -->
+        <com.atmob.keyboard_android.component.child.impl.AiKeyboardProloguePanelComponent
+            android:id="@+id/prologue_panel"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:orientation="horizontal">
-
-            <com.atmob.keyboard_android.component.base.RouteComponent
-                android:id="@+id/route_component"
-                android:layout_width="0dp"
-                android:layout_height="wrap_content"
-                android:layout_marginStart="10dp"
-                android:layout_marginEnd="3dp"
-                android:layout_weight="1">
-
-                <com.atmob.keyboard_android.component.base.FakeComponent
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content">
-
-                    <androidx.recyclerview.widget.RecyclerView
-                        android:id="@+id/key_list"
-                        android:layout_width="match_parent"
-                        android:layout_height="wrap_content"
-                        tools:layout_height="150dp" />
-                </com.atmob.keyboard_android.component.base.FakeComponent>
-
-                <com.atmob.keyboard_android.component.child.impl.AiChatComponent
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:visibility="gone"
-                    tools:visibility="visible" />
-            </com.atmob.keyboard_android.component.base.RouteComponent>
-
-            <LinearLayout
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginEnd="10dp"
-                android:orientation="vertical">
-
-                <com.atmob.keyboard_android.widget.LongTouchContainer
-                    android:id="@+id/delete_btn"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_marginBottom="6dp">
-
-                    <TextView
-                        style="@style/keyboard_action_btn"
-                        android:layout_marginBottom="0dp"
-                        android:text="@string/delete" />
-                </com.atmob.keyboard_android.widget.LongTouchContainer>
-
-                <TextView
-                    android:id="@+id/clear_btn"
-                    style="@style/keyboard_action_btn"
-                    android:text="@string/clear" />
-
-                <TextView
-                    android:id="@+id/send_btn"
-                    style="@style/keyboard_action_btn"
-                    android:text="@string/send" />
-            </LinearLayout>
-        </LinearLayout>
-    </LinearLayout>
+            android:visibility="gone" />
+    </com.atmob.keyboard_android.component.base.RouteComponent>
 </LinearLayout>

+ 105 - 0
plugins/keyboard_android/android/src/main/res/layout/component_ai_keyboard_common_panel.xml

@@ -0,0 +1,105 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/keyboard_content_height"
+    android:orientation="vertical"
+    tools:background="@mipmap/bg_keyboard"
+    tools:layout_height="wrap_content">
+
+    <LinearLayout
+        android:id="@+id/keyboard_key_content"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1"
+        android:orientation="vertical">
+
+        <LinearLayout
+            android:id="@+id/paste_bar_layout"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="6dp"
+            android:orientation="horizontal">
+
+            <com.atmob.keyboard_android.component.child.impl.PasteBarComponent
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:layout_marginStart="10dp"
+                android:layout_weight="1"
+                tools:background="@android:color/holo_red_dark"
+                tools:layout_height="46dp" />
+
+            <TextView
+                android:id="@+id/paste_btn"
+                style="@style/keyboard_action_btn2"
+                android:layout_marginStart="10dp"
+                android:layout_marginTop="6dp"
+                android:layout_marginEnd="10dp"
+                android:text="@string/paste" />
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+
+            <com.atmob.keyboard_android.component.base.RouteComponent
+                android:id="@+id/common_route_component"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="10dp"
+                android:layout_marginEnd="3dp"
+                android:layout_weight="1">
+
+                <com.atmob.keyboard_android.component.base.FakeComponent
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content">
+
+                    <androidx.recyclerview.widget.RecyclerView
+                        android:id="@+id/key_list"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        tools:layout_height="150dp" />
+                </com.atmob.keyboard_android.component.base.FakeComponent>
+
+                <com.atmob.keyboard_android.component.child.impl.AiChatComponent
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:visibility="gone"
+                    app:uic_component_name="common.AiChatComponent"
+                    tools:visibility="visible" />
+            </com.atmob.keyboard_android.component.base.RouteComponent>
+
+            <LinearLayout
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginEnd="10dp"
+                android:orientation="vertical">
+
+                <com.atmob.keyboard_android.widget.LongTouchContainer
+                    android:id="@+id/delete_btn"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginBottom="6dp">
+
+                    <TextView
+                        style="@style/keyboard_action_btn"
+                        android:layout_marginBottom="0dp"
+                        android:text="@string/delete" />
+                </com.atmob.keyboard_android.widget.LongTouchContainer>
+
+                <TextView
+                    android:id="@+id/clear_btn"
+                    style="@style/keyboard_action_btn"
+                    android:text="@string/clear" />
+
+                <TextView
+                    android:id="@+id/send_btn"
+                    style="@style/keyboard_action_btn"
+                    android:text="@string/send" />
+            </LinearLayout>
+        </LinearLayout>
+    </LinearLayout>
+</LinearLayout>

+ 100 - 0
plugins/keyboard_android/android/src/main/res/layout/component_ai_keyboard_prologue_panel.xml

@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/keyboard_content_height"
+    android:orientation="vertical"
+    tools:background="@mipmap/bg_keyboard"
+    tools:layout_height="wrap_content">
+
+    <FrameLayout
+        android:id="@+id/tar_bar_layout"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_horizontal"
+        android:layout_marginTop="14dp"
+        android:layout_marginBottom="14dp"
+        android:background="@drawable/bg_tab_indicator"
+        android:paddingStart="2.5dp"
+        android:paddingTop="2dp"
+        android:paddingEnd="2.5dp"
+        android:paddingBottom="2dp">
+
+        <net.lucode.hackware.magicindicator.MagicIndicator
+            android:id="@+id/magic_indicator"
+            android:layout_width="wrap_content"
+            android:layout_height="@dimen/common_navigator_height"
+            android:layout_gravity="center" />
+    </FrameLayout>
+
+    <LinearLayout
+        android:id="@+id/keyboard_key_content"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1"
+        android:orientation="vertical">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+
+            <com.atmob.keyboard_android.component.base.RouteComponent
+                android:id="@+id/prologue_route_component"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="10dp"
+                android:layout_marginEnd="3dp"
+                android:layout_weight="1">
+
+                <com.atmob.keyboard_android.component.base.FakeComponent
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content">
+
+                    <androidx.recyclerview.widget.RecyclerView
+                        android:id="@+id/key_list"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        tools:layout_height="150dp" />
+                </com.atmob.keyboard_android.component.base.FakeComponent>
+
+                <com.atmob.keyboard_android.component.child.impl.AiChatComponent
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:visibility="gone"
+                    app:uic_component_name="prologue.AiChatComponent"
+                    tools:visibility="visible" />
+            </com.atmob.keyboard_android.component.base.RouteComponent>
+
+            <LinearLayout
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginEnd="10dp"
+                android:orientation="vertical">
+
+                <com.atmob.keyboard_android.widget.LongTouchContainer
+                    android:id="@+id/delete_btn"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginBottom="6dp">
+
+                    <TextView
+                        style="@style/keyboard_action_btn"
+                        android:layout_marginBottom="0dp"
+                        android:text="@string/delete" />
+                </com.atmob.keyboard_android.widget.LongTouchContainer>
+
+                <TextView
+                    android:id="@+id/clear_btn"
+                    style="@style/keyboard_action_btn"
+                    android:text="@string/clear" />
+
+                <TextView
+                    android:id="@+id/send_btn"
+                    style="@style/keyboard_action_btn"
+                    android:text="@string/send" />
+            </LinearLayout>
+        </LinearLayout>
+    </LinearLayout>
+</LinearLayout>