chore: initialize qiming workspace repository

This commit is contained in:
Codex
2026-05-29 14:22:48 +08:00
commit bfd67a0f2c
10750 changed files with 1885711 additions and 0 deletions

View File

@@ -0,0 +1,92 @@
export default `
1. 这是1.1 *斜体*
- 这是1.1.1
- 这是1.1.2
2. 这是1.2
- 这是1.2.1
- 这是1.2.1.1
- 这是1.2.1.2
- 这是1.2.2
- 这是1.2.2.1
- 这是1.2.2.2
3. 这是1.3
- 这是1.3.1
- 这是1.3.2
---
[**加粗的链接**](https://example2.com)
[*斜体的链接*](https://example2.com)
[~~删除的链接~~](https://example2.com)
\`\`\` html
<div>这是一个html代码块</div>
\`\`\`
\`\`\` javascript
function example() {
return 'Hello, World!';
}
\`\`\`
\`\`\` css
.example {
color: red;
color: #666;
}
\`\`\`
**加粗**
*斜体*
1. *斜体* 这是一个有序列表的第一个
- 团队选择本 Sprint 要完成的任务,拆解为具体工作。
2. **加粗** 这是一个有序列表的第二个
3. ## 列表 + 一个井号的标题(H1)
这是一个段落,链接前面的文字 [link](https://example.com) 这是链接后面的文字
# *井号+斜体*
[link2](https://example2.com)
# 一个井号的标题(H1)
123
## 两个井号的标题(H2)
### 三个井号的标题
#### 四个井号的标题
##### 五个井号的标题
**加粗**
*斜体*
~~删除线~~
# **加粗**
#### 无序列表
- 这是一个无序列表的第一个
- 这是一个无序列表的第二个
## 有序列表
1. 这是一个有序列表的第一个
2. 这是一个有序列表的第二个
3. 这是一个有序列表的第二个
> 这是一个引用
| 表格标题 1 | 表格标题 2 |
| ---- | ---- |
| 表格内容 1 | 表格内容 2表格内容 2表格内容 2表格内容 2表格内容 2表格内容 2表格内容 2 |
| 表格内容 1 | 表格内容 2 |
| 表格内容 1 | 表格内容 2 |
`

View File

@@ -0,0 +1,114 @@
<template>
<view class="tool-bar-container">
<text class="iconfont icon-Copy btn" @tap="copy"></text>
<text class="time">{{createTime}}</text>
</view>
</template>
<script lang="uts" setup>
import { MsgItem } from '@/uni_modules/uni-ai-x/sdk';
import { formatTimeAgo } from '@/utils/common';
import { useI18n } from '@/utils/i18n';
const { t } = useI18n()
// 使用 ref 来存储时间,自动更新时间
const createTime = ref<string>('')
const props = defineProps<{
msg: MsgItem
}>()
const msg = computed<MsgItem>(() => props.msg as MsgItem)
// 更新时间的函数
const updateTime = () => {
createTime.value = formatTimeAgo(msg.value.create_time)
}
// 定时器ID
let timerId: number | null = null
// 启动定时器,每分钟更新一次
const startTimer = () => {
// 立即更新一次
updateTime()
// 每分钟更新一次60000毫秒
timerId = setInterval(() => {
updateTime()
}, 60000)
}
// 停止定时器
const stopTimer = () => {
if (timerId !== null) {
clearInterval(timerId)
timerId = null
}
}
onMounted(() => {
startTimer()
})
// 组件卸载时清理定时器
onUnmounted(() => {
stopTimer()
})
const copy = () => {
// 兼容性处理
const text = msg.value.text || msg.value.body
if (typeof text != 'string') {
uni.showToast({
title: t('Mobile.ThirdParty.UniAiX.invalidMessageContent'),
icon: 'none',
})
return
}
uni.setClipboardData({
data: text as string,
success: () => {
uni.showToast({
title: t('Mobile.Common.copySuccess'),
icon: 'success',
})
},
fail: () => {
uni.showToast({
title: t('Mobile.Common.copyFailed'),
icon: 'none',
})
}
})
}
</script>
<style lang="scss">
.tool-bar-container {
width: 100%;
padding: 8rpx 0 16rpx 0;
margin-top: 16rpx;
display: flex;
flex-direction: row;
align-items: center;
.btn {
font-size: 36rpx;
margin-right: 32rpx;
// #ifdef WEB
cursor: pointer;
&:hover {
filter: brightness(0.8);
}
// #endif
}
.time {
margin-left: auto;
font-size: 28rpx;
color: rgba(0, 0, 0, 0.45);
font-weight: 400;
}
}
</style>

View File

@@ -0,0 +1,76 @@
<template>
<text :decode="true" :style="{ color: color, 'font-size': iconSize }" @click="_onClick" class="uni-ai-icon">{{unicode}}</text>
</template>
<script lang="uts" setup>
// #ifdef APP
function decodeUnicode(code: string): string {
// 将字符串分割成 Unicode 编码的字符
let codes = code.split('%u');
// 遍历字符数组
let chars = codes.map(code => {
// 转换为十六进制
let hexCode = parseInt(code, 16);
// 将 UTF-16 编码转换为字符
return String.fromCharCode(hexCode);
});
// 将字符数组转换成字符串
return chars.join('');
}
// #endif
const getVal = (val: string) :string => {
const reg = /^[0-9]*$/g
return (typeof val === 'number' || reg.test(val) )? val + 'px' : val;
}
type UniAiIconProps = {
code?: string
color?: string
size?: string
}
const props = withDefaults(defineProps<UniAiIconProps>(), {
code: '',
color: '#333333',
size: '16'
})
const emit = defineEmits<{
'click': [e: UniEvent]
}>()
const unicode = computed<string>(() => {
// #ifdef APP
return decodeUnicode(props.code)
// #endif
// #ifndef APP
return unescape(`%u${props.code}`) as string
// #endif
})
const iconSize = computed<string>(() => {
return getVal(props.size)
})
const _onClick = (e: UniEvent) => {
emit('click', e)
}
</script>
<style>
@font-face {
// #ifdef WEB
font-display: swap;
// #endif
font-family: "uni-ai-icon";
src: url('/uni_modules/uni-ai-x/static/font/iconfont.ttf');
}
.uni-ai-icon {
font-family: uni-ai-icon !important;
font-size: 16px;
font-style: normal;
/* #ifdef H5 */
cursor: pointer;
/* #endif */
}
</style>

View File

@@ -0,0 +1,506 @@
<template>
<view class="msg-root">
<text v-if="msg.error_msg != null" class="error-msg">
{{ msg.error_msg }}
</text>
<template v-else>
<!-- AI 思考模式 -->
<view class="think-container" v-if="!!msg.thinkContent || !!msg.think">
<view class="think-header">
<view class="think-header-left" @tap="changeThinkContent">
<text class="iconfont icon-BulbOutlined thinking-icon"></text>
<text class="thinking-title">
{{
isThinkingFinished
? t("Mobile.Chat.thought")
: t("Mobile.Chat.thinking")
}}
</text>
<text
class="iconfont icon-a-Chevrondown expand-icon"
:class="{ 'is-expanded': !hideThinkContent }"
></text>
</view>
</view>
<view
class="think-content"
:class="{ 'hide-think-content': hideThinkContent }"
>
<view class="text-box">
<mp-html
:content="msg.thinkContent || msg.think"
:markdown="true"
:selectable="true"
:container-style="`font-size: 28rpx; color: #888; line-height: 1.6;`"
/>
</view>
</view>
</view>
<!-- AI 答复内容 -->
<view class="answer-container">
<!-- AI校验通过后需要水平滚动表格时需要设置 scroll-table="true" -->
<mp-html
:content="processedText"
:processing-list="msg.processingList"
:conversation-id="msg.chat_id"
:markdown="true"
:selectable="true"
:container-style="`width: 100%;`"
:tag-style="tagStyle"
:scroll-table="true"
/>
</view>
</template>
<image
v-if="
isConversationActive && isLastMsg && taskStatus !== TaskStatus.EXECUTING
"
class="icon-loading-image"
src="@/static/assets/icon_loading.svg"
:show-menu-by-longpress="false"
@longtap.stop.prevent
@contextmenu.stop.prevent="true"
mode="widthFix"
/>
<msg-tool-bar
v-if="
((!isConversationActive || !isLastMsg) && (msg.body || msg.text)) ||
msg.state === 3
"
:msg="msg"
/>
</view>
</template>
<script lang="uts" setup>
import { MsgItem } from "@/uni_modules/uni-ai-x/sdk";
import msgToolBar from "@/uni_modules/uni-ai-x/components/msg-tool-bar.uvue";
import mpHtml from "@/subpackages/uni_modules/mp-html/components/mp-html/mp-html.vue";
import {
replaceMathBracket,
groupMarkdownContainers,
} from "@/utils/markdown.uts";
import { TaskStatus } from "@/types/enums/agent";
import { useI18n } from "@/utils/i18n";
const { t } = useI18n();
const props = defineProps<{
msg: MsgItem;
// 会话是否正在进行中(有消息正在处理)
isConversationActive?: boolean;
taskStatus?: TaskStatus;
// 是否是最后一条消息
isLastMsg?: boolean;
}>();
const msg = ref<MsgItem>(props.msg as MsgItem); // 转化内部状态为响应式
const isLastMsg = computed(() => props.isLastMsg);
const isConversationActive = computed(() => props.isConversationActive);
// 思考内容是否隐藏(默认隐藏)
const hideThinkContent = ref<boolean>(true);
// 思考是否已完成:如果正文已经有内容,或者会话已结束,或者不是最后一条消息,则认为思考已完成
const isThinkingFinished = computed((): boolean => {
return (
msg.value.body.trim().length > 0 ||
!props.isConversationActive ||
!props.isLastMsg
);
});
// 标签自定义样式,解决长文本换行问题
const tagStyle = {
p: "word-break: break-all; overflow-wrap: break-word;",
li: "word-break: break-all; overflow-wrap: break-word;",
div: "word-break: break-all; overflow-wrap: break-word;",
span: "word-break: break-all; overflow-wrap: break-word;",
};
const emit = defineEmits<{
changeThinkContent: [hideThinkContent: boolean];
}>();
const getRenderKey = (...args: any[]) => {
return msg.value.chat_id + "-" + args.join("-");
};
const genImageHtml = (src: string) => {
return `<img src="${src}" />`;
};
const changeThinkContent = () => {
hideThinkContent.value = !hideThinkContent.value;
emit("changeThinkContent", hideThinkContent.value);
};
// 添加updateMessage方法支持动态更新消息内容
const updateMessage = (newData: any) => {
try {
// 使用响应式数据而不是直接操作props
if (newData) {
const updatedMsg = {
...msg.value,
...newData,
};
msg.value = updatedMsg;
// console.log('更新消息成功:', newData);
}
} catch (error) {
console.error("updateMessage 执行失败:", error);
}
};
// 处理文本内容,应用数学公式格式转换及组件合并
const processedText = computed(() => {
if (!msg.value || (msg.value.body || "").trim().length === 0) {
return "";
}
const groupedText = groupMarkdownContainers(msg.value.body);
return replaceMathBracket(groupedText);
});
// 暴露方法给父组件
defineExpose({
updateMessage,
});
</script>
<style lang="scss">
.msg-root {
flex-direction: row;
flex-wrap: wrap;
// #ifdef WEB || MP-WEIXIN
user-select: text;
-webkit-user-select: text;
// #endif
padding: 0 32rpx;
min-height: 90rpx;
.think-container {
width: 100%;
// margin-bottom: 24rpx;
.think-header {
display: flex;
flex-direction: row;
align-items: center;
padding: 8rpx 0;
.think-header-left {
display: flex;
flex-direction: row;
align-items: center;
.thinking-icon {
font-size: 36rpx;
color: #999;
margin-right: 12rpx;
}
.thinking-title {
color: #999;
font-size: 28rpx;
}
.expand-icon {
margin-left: 12rpx;
font-size: 28rpx;
color: #999;
transition: transform 0.3s ease;
&.is-expanded {
transform: rotate(180deg);
}
}
}
}
.think-content {
overflow: hidden;
transition: all 0.3s ease;
padding-top: 8rpx;
&.hide-think-content {
height: 0;
padding-top: 0;
opacity: 0;
}
.text-box {
border-left: 4rpx solid #efefef;
padding-left: 20rpx;
background-color: rgba(245, 245, 245, 0.3);
border-radius: 0 8rpx 8rpx 0;
padding-top: 8rpx;
padding-bottom: 8rpx;
.text {
font-size: 28rpx;
color: #888;
line-height: 1.6;
white-space: pre-wrap;
word-break: break-all;
}
}
}
}
.answer-container {
width: 100%;
}
.error-msg {
color: #ff4d4f;
font-size: 28rpx;
line-height: 52rpx;
}
.loging-icon {
margin-top: 16rpx;
width: 32rpx;
height: 32rpx;
}
.stop-text {
color: #aaa;
font-size: 28rpx;
padding: 16rpx 0;
}
.need-try {
color: #ff4d4f;
font-size: 28rpx;
padding: 16rpx;
}
.error-msg,
.stop-text,
.need-try {
text-align: left;
width: 100%;
}
}
.text {
color: #15171f;
font-weight: 400;
line-height: 56rpx;
white-space: pre-wrap;
word-break: break-all;
overflow-wrap: anywhere;
max-width: 100%;
}
.strong {
font-weight: bold;
}
.em {
font-style: italic;
}
.del {
text-decoration-line: line-through;
color: #999;
}
.space {
height: 10px;
}
.link {
color: #0066cc !important;
word-break: break-all;
overflow-wrap: anywhere;
// #ifdef WEB
cursor: pointer;
&:hover {
opacity: 0.8;
}
// #endif
}
.paragraph-box {
display: block;
.text {
font-size: 32rpx;
font-weight: 400;
line-height: 56rpx;
// 自动换行并显示 \n
white-space: pre-wrap;
word-break: break-all;
overflow-wrap: anywhere;
max-width: 100%;
/* #ifdef H5 */
width: auto;
word-break: break-all;
/* #endif */
}
}
.image-box {
width: 100%;
}
.blockquote-box {
padding: 5px;
background: #f5f5f5;
border-left: 4px solid #ddd;
}
.container-box {
width: 100%;
}
.heading-box {
.text {
color: #15171f;
font-size: 40rpx;
font-weight: 600;
line-height: 56rpx;
}
.depth-1 {
font-size: 50rpx;
line-height: 60rpx;
}
.depth-2 {
font-size: 42rpx;
line-height: 56rpx;
}
.depth-3 {
font-size: 34rpx;
line-height: 56rpx;
}
.depth-4 {
font-size: 30rpx;
line-height: 56rpx;
}
.depth-5 {
font-size: 26rpx;
line-height: 56rpx;
}
.depth-6 {
font-size: 24rpx;
color: #999;
line-height: 56rpx;
}
}
.hr {
height: 2rpx;
background: #ddd;
margin: 20rpx 0;
}
.br {
width: 100%;
display: block;
}
.list-box {
padding-left: 10rpx;
.list-content {
width: 100%;
}
.text {
line-height: 56rpx;
}
.list-item-content-box {
flex: 1;
.list {
margin-top: 0;
}
}
.list-item-br {
width: 100%;
}
.list-item-index {
width: 40rpx;
&.unordered {
font-size: 28rpx;
color: #000;
}
&.task {
font-size: 28rpx;
color: #666;
}
}
}
.list-inline-image-box {
max-width: 100%;
display: block;
.list-content {
width: 100%;
}
.text {
line-height: 56rpx;
}
.list-item-content-box {
flex: 1;
.list {
margin-top: 0;
}
}
.list-item-br {
width: 100%;
}
.list-item-index {
width: 40rpx;
&.unordered {
font-size: 28rpx;
color: #000;
}
&.task {
font-size: 28rpx;
color: #666;
}
}
}
.hr-box {
background-color: #ddd;
height: 2rpx;
}
.tool-call-box {
width: 100%;
margin: 10rpx 0;
}
.table-box,
.code-box,
.hr-box,
.paragraph-box,
.blockquote-box,
.heading-box,
.list-box,
.loging-icon,
.math-box {
width: 100%;
margin: 10rpx 0;
}
.paragraph-box {
margin-top: 0;
margin-bottom: 0;
}
.space-box {
height: 30rpx;
}
.codespan {
color: #666;
background-color: #f5f5f5;
padding: 4rpx 8rpx;
border-radius: 10rpx;
font-size: 28rpx;
word-break: break-all;
overflow-wrap: anywhere;
}
.icon-loading-image {
width: 48rpx;
height: 48rpx;
margin-right: 10rpx;
-webkit-touch-callout: none !important;
-webkit-user-select: none !important;
user-select: none !important;
}
</style>

View File

@@ -0,0 +1,35 @@
<template>
<view class="rotate-icon" id="rotate-icon" @transitionend="updateRotate"
:style="{
transform: `rotate(${times * 360}deg)`
}"
></view>
</template>
<script lang="uts" setup>
const times = ref<number>(0)
const updateRotate = () => {
times.value++
}
onMounted(() => {
setTimeout(() => {
updateRotate()
}, 300)
})
</script>
<style lang="scss">
.rotate-icon {
width: 16px;
height: 16px;
border-radius: 100px;
border-top: solid 1px #AAA;
border-right: solid 1px #AAA;
margin:0 5px;
/* 旋转动画 */
transition-property: transform;
transition-timing-function: linear;
transform: rotate(0deg);
transition-duration: 1000ms;
}
</style>