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>

View File

@@ -0,0 +1,105 @@
import {reactive} from 'vue';
import {LLMModel, Provider, DefaultLLM, BailianTokenInfo,Userinfo} from '@/uni_modules/uni-ai-x/types.uts';
// 1. 定义左侧菜单区域下方的用户头像信息
export const currentUser: Userinfo = reactive<Userinfo>({})
// 这里以uni-id的用户信息绑定为例你自己的系统如果不是基于 unicloud 的,请参考此方法修改
// import {watch} from 'vue';
// 导入 uni-id 中的用户信息
// import {state as uniIdState} from '@/uni_modules/uni-id-pages-x/store.uts';
// 监听用户信息变化,更新 currentUser 对象
// watch((): UTSJSONObject => uniIdState.userInfo,(userInfo: UTSJSONObject)=>{
// currentUser._id = userInfo.getString('_id')
// currentUser.nickname = userInfo.getString('nickname')
// // 根据 file_id 获取文件的 url
// const avatarFileUrl = userInfo.getString('avatar_file.url')
// if (avatarFileUrl != null) {
// uniCloud.getTempFileURL({fileList: [avatarFileUrl]}).then((res) => {
// currentUser.avatar_file = {url: res.fileList[0].tempFileURL}
// })
// } else {
// currentUser.avatar_file = null
// }
// }, {deep: true, immediate: true})
// 2. 点击左侧菜单区域下方的用户头像信息后跳转的页面路径
// export const userCenterPage = '/uni_modules/uni-id-pages-x/pages/userinfo/userinfo?showLoginManage=true'
// 3. 配置大语言模型
/**
* 配置大语言模型
* 注意需要在,目录:`/uni_modules/uni-ai-x/static/ai-provider/${provider}.png` 中添加对应的logo图片
* 默认以第一个为默认模型
*/
const bailianTokenInfo: BailianTokenInfo = {token: '', expireTime: 0}
const llmModelMap = new Map<string, Provider>([
// 阿里云百炼
[
'bailian', {
models: [
{ name: 'deepseek-v3'},
{ name: 'deepseek-r1', thinking: true },
{ name: 'qwen-turbo'},
{ name: 'qwen-plus', thinking: true }
],
description: '阿里云百炼 - 专注AI技术研究和应用',
baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions",
async getToken(): Promise<string> {
// 如果token存在且未过期则直接返回
if (bailianTokenInfo.expireTime > Date.now()) {
return bailianTokenInfo.token
}
// unicloud项目 通过uni-ai-x-co 获取临时token
const uniAiChatCo = uniCloud.importObject("uni-ai-x-co", { customUI: true })
try {
const getTmpTokenRes = await uniAiChatCo.getBailianTmpToken()
if (getTmpTokenRes.errCode != 0) {
throw new Error('获取临时token失败')
}
const tmpToken = getTmpTokenRes.getString('data.token', '')
const expiresAt = getTmpTokenRes.getNumber('data.expiresAt', 0)
bailianTokenInfo.token = tmpToken
bailianTokenInfo.expireTime = expiresAt
return tmpToken
} catch (e: any) {
// const error = JSON.parse<UniCloudError>(JSON.stringify(e)) as UniCloudError
// console.error('uni-ai-x-co 请求失败', error, error.message)
throw new Error('获取临时token失败')
}
// 非 unicloud项目 请求自己的服务器的接口得到临时 token 例如
/*
const res = await uni.request({
url: 'https://your-server.com/get-token',
method: 'GET'
})
if (res.statusCode != 200) {
throw new Error('获取临时token失败')
}
const tmpToken = res.getString('data.token', '')
const expiresAt = res.getNumber('data.expiresAt', 0)
bailianTokenInfo.token = tmpToken
bailianTokenInfo.expireTime = expiresAt
return tmpToken
*/
}
}
]
])
const providerNameList: string[] = []
llmModelMap.forEach((_, key: string) => {
providerNameList.push(key)
})
if (providerNameList.length < 1) {
throw new Error('至少需要配置一个大语言模型提供商')
}
const defaultLLM: DefaultLLM = {
provider: providerNameList[0]!,
model: llmModelMap.get(providerNameList[0])?.models?.[0].name!
}
export default llmModelMap
export { llmModelMap, type LLMModel, providerNameList, defaultLLM }

View File

@@ -0,0 +1,106 @@
{
"id": "uni-ai-x",
"displayName": "uni-ai x",
"version": "1.0.7",
"description": "一个开源的、全平台的、原生的、云端一体的ai聊天套件",
"keywords": [
"uni-ai-x",
"uni-ai-chat",
"ai",
"deepseek"
],
"repository": "https://gitcode.com/dcloud/uni-ai-x",
"engines": {
"HBuilderX": "^3.1.0",
"uni-app": "",
"uni-app-x": "^4.75"
},
"dcloudext": {
"type": "unicloud-template-page",
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "",
"darkmode": "x",
"i18n": "x",
"widescreen": "√"
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "√",
"aliyun": "√",
"alipay": "√"
},
"client": {
"uni-app": {
"vue": {
"vue2": "-",
"vue3": "-"
},
"web": {
"safari": "-",
"chrome": "-"
},
"app": {
"vue": "-",
"nvue": "-",
"android": "-",
"ios": "-",
"harmony": "-"
},
"mp": {
"weixin": "-",
"alipay": "-",
"toutiao": "-",
"baidu": "-",
"kuaishou": "-",
"jd": "-",
"harmony": "-",
"qq": "-",
"lark": "-"
},
"quickapp": {
"huawei": "-",
"union": "-"
}
},
"uni-app-x": {
"web": {
"safari": "-",
"chrome": "√"
},
"app": {
"android": {
"extVersion": "",
"minVersion": "21"
},
"ios": "√",
"harmony": "√"
},
"mp": {
"weixin": {
"extVersion": "1.0.2",
"minVersion": ""
}
}
}
}
}
}
}

View File

@@ -0,0 +1,4 @@
// 只导出类型定义,不导出完整 SDK 功能
// 项目只使用了 uni-ai-x 的类型定义,完整功能已移除以减小包体积
import { MsgItem, ChatItem, BaseMsgItem, Userinfo, markdownElItem } from '@/uni_modules/uni-ai-x/types.uts';
export { MsgItem, ChatItem, BaseMsgItem, Userinfo, markdownElItem }

View File

@@ -0,0 +1,154 @@
// 定义简化的 MarkdownToken 类型,不依赖 kux-marked
export type MarkdownToken = {
type: string,
raw?: string,
text?: string | null,
href?: string | null,
title?: string | null,
depth?: number,
lang?: string | null,
tokens?: MarkdownToken[] | null,
ordered?: boolean,
start?: number | null,
loose?: boolean,
items?: MarkdownToken[],
task?: boolean,
checked?: boolean | null,
codeBlockStyle?: string | null,
escaped?: boolean,
[key: string]: any // 允许其他属性
}
export type LLMModel = {
name: string,
thinking?: boolean,
webSearch?: boolean
}
export type Provider = {
models: LLMModel[],
description?: string,
baseURL?: string,
getToken?: () => Promise<string>,
}
export type DefaultLLM = {
provider: string,
model: string
}
export type BailianTokenInfo = {
token: string,
expireTime: number
}
export type Llm = {
provider: string
model: string
webSearch: boolean
}
// 对话
export type Chat = {
datas: ChatItem[],
activeId: string
}
// 对话
export type ChatItem = {
id: string,
title: string,
state: string,
inputContent: string,
msgList: MsgItem[],
update_time: number
}
// 流式响应分块
type ChunkChoiceDelta = {
content: string,
reasoning_content?: string
}
type ChunkMessage = {
content: string
}
type ChunkChoice = {
delta?: ChunkChoiceDelta
message?: ChunkMessage
}
type Output = {
text: string
}
export type Chunk = {
choices?: ChunkChoice[],
output?: Output,
DONE?: boolean,
}
export type markdownElItem = {
uniqueId: string,
type: string,
datasList: MarkdownToken[][]
}
// 消息
export type MsgItem = {
_id: string
body: string
thinkContent?: string
from_uid: string
state: number
create_time: number
// 如果是ai回复的则需要关联的msg_id
about_msg_id?: string
// 所属对话id
chat_id: string,
markdownElList: markdownElItem[]
error_msg?: string
rendered: boolean
// 自定义组件处理列表(用于历史消息中的工具调用等)
processingList?: any[]
}
export type BaseMsgItem = {
body: string
from_uid: string
about_msg_id?: string
}
// 消息对象,含:消息列表、是否还有更多消息、是否正在加载更多消息、加载更多消息方法
export type Msg = {
dataList: MsgItem[],
hasMore: boolean,
loading: boolean,
loadMore: () => void
}
// 请求ai对话消息
export type RequestAiCoMessage = {
// 来自谁用户或ai
role : "user" | "assistant"
// 内容
content : string
}
// 用户
export type Userinfo = {
_id?: string
nickname?: string
avatar_file?: AvatarFile
}
export type AvatarFile = {
url?: string
file_id?: string
// 略...
}
// 用户
export type Theme = "auto" | "dark" | "light"
export type Setting = {
// 主题
theme: Theme
// 语言
language: "zh-CN" | "en-US"
}