新增: 小程序端跨端AI对话

This commit is contained in:
2026-06-24 00:19:54 +08:00
parent 0c7c5e6068
commit 698d981962
20 changed files with 5647 additions and 1242 deletions

View File

@@ -0,0 +1,36 @@
<template>
<!-- rich-text 小程序原生组件(无子组件依赖,)marked mdhtml rich-text :nodes 渲染
alpha 根因已查实(uni-app 2026 alpha tap 失效),rich-text 非元凶,降级后正常用 -->
<rich-text :nodes="html" :selectable="true" />
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { marked } from 'marked'
import { styleMarkdown } from '@/utils/mdRenderer'
defineOptions({ name: 'MdView' })
/**
* Markdown 渲染气泡(miniapp,rich-text 原生方案)。
*
* 链路:marked.parse(md) → styleMarkdown 注入 inline style → rich-text :nodes(原生渲染 html 节点)。
* - breaks:true 聊天换行(单换行转 <br>)
* - P1-G:styleMarkdown 给代码块/链接/表格加 inline style(rich-text 不认 class)
* - parse 失败回退原文
* - 空内容返空(rich-text 不渲染)
*
* 流式:currentText 变化 → html computed 重算 → rich-text 重渲染。
*/
const props = defineProps<{ content: string }>()
const html = computed(() => {
const src = props.content || ''
if (!src) return ''
try {
return styleMarkdown(marked.parse(src, { breaks: true, async: false }) as string)
} catch {
return src
}
})
</script>

View File

@@ -0,0 +1,133 @@
<template>
<!-- @/ 联想弹层(MVP)
固定占位列表:不接真实技能路由/实体检索,仅作 chip 插入
/file /web /task /memory + @file @web @task 四类技能占位
渲染对齐 chat/index.vue mp-weixin 稳定风格(原生 view/text + 绝对定位浮层) -->
<view v-if="visible" class="mention-mask" @tap="onClose">
<view class="mention-pop" @tap.stop>
<view class="mention-head">
<text class="mention-title">{{ trigger === '/' ? '技能' : '提及' }}</text>
<text class="mention-hint">MVP 占位 · 点击插入</text>
</view>
<scroll-view class="mention-list" scroll-y>
<view
v-for="(item, idx) in items"
:key="idx"
class="mention-item"
@tap="onSelect(item)"
>
<text class="mention-item-label">{{ item.label }}</text>
<text class="mention-item-insert">{{ item.insert.trim() }}</text>
</view>
</scroll-view>
</view>
</view>
</template>
<script setup lang="ts">
/**
* MentionInput 联想弹层组件(MVP)。
*
* 设计:
* - 纯展示:接收 visible/trigger/items,选中后 emit select(insert) + close
* - 不查后端/不调路由:items 由父组件固定注入
* - 触发字符 `/`(技能)或 `@`(提及)由父组件依据光标位置判定
* - 选中即在 inputText 末尾插入对应字面量(如 `/file `),发送时整体纯文本走 ai.send
*
* props:
* - visible:是否显示
* - trigger:触发类型(`/` 技能 / `@` 提及)
* - items:占位项列表 {label, insert}
* emits:
* - select(insert):插入文本
* - close():关闭弹层
*/
defineOptions({ name: 'MentionInput' })
interface MentionItem {
label: string
insert: string
}
defineProps<{
visible: boolean
trigger: '/' | '@'
items: MentionItem[]
}>()
const emit = defineEmits<{
(e: 'select', insert: string): void
(e: 'close'): void
}>()
function onSelect(item: MentionItem): void {
emit('select', item.insert)
}
function onClose(): void {
emit('close')
}
</script>
<style scoped>
.mention-mask {
position: fixed;
left: 0;
right: 0;
bottom: 0;
top: 0;
background-color: rgba(0, 0, 0, 0.3);
z-index: 999;
}
.mention-pop {
position: absolute;
left: 12px;
right: 12px;
bottom: 56px;
max-height: 260px;
background-color: #1f1f1f;
border: 1px solid #333333;
border-radius: 10px;
overflow: hidden;
display: flex;
flex-direction: column;
}
.mention-head {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 12px;
background-color: #262626;
border-bottom: 1px solid #2e2e2e;
}
.mention-title {
color: #e0e0e0;
font-size: 13px;
font-weight: bold;
}
.mention-hint {
color: #777777;
font-size: 11px;
}
.mention-list {
max-height: 220px;
}
.mention-item {
display: flex;
flex-direction: column;
padding: 10px 12px;
border-bottom: 1px solid #2a2a2a;
}
.mention-item:last-child {
border-bottom: none;
}
.mention-item-label {
color: #e0e0e0;
font-size: 14px;
}
.mention-item-insert {
color: #4a9eff;
font-size: 12px;
margin-top: 2px;
}
</style>