@vyr/service-chat 0.0.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,85 @@
1
+ <template>
2
+ <div v-if="tasks.length" class="chat-executor">
3
+ <ExecutorHeader :title="title" :spinning="hasRunningTask" :completed="completedCount" :total="totalCount" />
4
+
5
+ <div class="tools-list">
6
+ <ToolItem v-for="task in tasks" :key="task.toolCallId" :task="task" :expanded="isTaskExpanded(task.toolCallId)"
7
+ @toggle="handleToggle" @copy="handleCopy" />
8
+ </div>
9
+ </div>
10
+ </template>
11
+
12
+ <script setup lang="ts">
13
+ import { computed } from 'vue';
14
+ import { language } from '../locale'
15
+ import { useExecutor } from './executor/useExecutor'
16
+ import { ExecutorProps, ExecutorEmits, ParamSectionType } from './executor/types'
17
+ import ExecutorHeader from './executor/ExecutorHeader.vue'
18
+ import ToolItem from './executor/ToolItem.vue'
19
+
20
+ const props = withDefaults(defineProps<ExecutorProps>(), {
21
+ tasks: () => [],
22
+ })
23
+
24
+ const title = computed(() => {
25
+ return completedCount.value === totalCount.value ? language.get('executor.task.execution.record') : language.get('executor.task.status.running')
26
+ })
27
+
28
+ const emit = defineEmits<ExecutorEmits>()
29
+
30
+ const {
31
+ hasRunningTask,
32
+ completedCount,
33
+ totalCount,
34
+ expandedTasks,
35
+ toggleTask,
36
+ isTaskExpanded,
37
+ copyToClipboard,
38
+ formatJSON
39
+ } = useExecutor(props)
40
+
41
+ // 处理任务展开/折叠
42
+ const handleToggle = (taskId: string | number) => {
43
+ toggleTask(taskId)
44
+ emit('task:toggle', taskId, expandedTasks.value[taskId])
45
+ }
46
+
47
+ // 处理复制
48
+ const handleCopy = async (type: string, taskId: string | number) => {
49
+ const task = props.tasks.find(t => t.toolCallId === taskId)
50
+ if (!task) return
51
+
52
+ let content: any
53
+ if (type === 'input') {
54
+ content = task.args
55
+ } else if (type === 'output') {
56
+ content = task.result
57
+ } else if (type === 'error') {
58
+ content = task.result
59
+ }
60
+
61
+ if (!content) return
62
+
63
+ const text = formatJSON(content)
64
+ await copyToClipboard(text, type as ParamSectionType, taskId, emit)
65
+ }
66
+ </script>
67
+
68
+ <style scoped>
69
+ .chat-executor {
70
+ background: #252526;
71
+ border-radius: 20px;
72
+ margin: 16px 0;
73
+ padding: 24px;
74
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2);
75
+ width: 100%;
76
+ border: 1px solid #3c3c3d;
77
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
78
+ }
79
+
80
+ .tools-list {
81
+ display: flex;
82
+ flex-direction: column;
83
+ gap: 12px;
84
+ }
85
+ </style>
@@ -0,0 +1,144 @@
1
+ <template>
2
+ <vyr-scroll ref="scrollRef" :padding="10" :margin="10" @wheel="onWheel">
3
+ <div class='chat-list'>
4
+ <template v-for="(msg, idx) in chatService.manager.window.history.list" :key="idx">
5
+ <McBubble v-if="msg.role === 'user'" :content="getTextContent(msg)" :align="'right'"
6
+ :avatarConfig="getAvatarConfig(msg)">
7
+ <template #bottom>
8
+ <div class="bubble-bottom-operations" v-if="msg.complete">
9
+ <McCopyIcon :text="getTextContent(msg)" class="copy-class" />
10
+ </div>
11
+ </template>
12
+ </McBubble>
13
+ <McBubble v-else :loading="msg.loading" :avatarConfig="getAvatarConfig(msg)" :data-v-idx="idx">
14
+ <template v-for="(chunk, i) in msg.chunks" :key="i">
15
+ <ChatExecutor v-if="chunk.type === 'tool'" :tasks="getTasks(chunk)"></ChatExecutor>
16
+ <McMarkdownCard v-else :content="getRenderContent(chunk)" :theme="themeService.currentTheme"
17
+ :enableThink="true" :mdPlugins="mdPlugins" :enableMermaid="true" />
18
+ </template>
19
+ <template #bottom>
20
+ <div class="bubble-bottom-operations" v-if="msg.complete">
21
+ <McCopyIcon :text="getTextContent(msg)" class="copy-class" />
22
+ </div>
23
+ </template>
24
+ </McBubble>
25
+ </template>
26
+ </div>
27
+ </vyr-scroll>
28
+ </template>
29
+
30
+ <script setup lang="ts">
31
+ import { nextTick, ref, useTemplateRef, watch } from 'vue';
32
+ import { runtime } from '@vyr/runtime';
33
+ import { themeService, VyrScroll } from '@vyr/design'
34
+ import { McBubble, McMarkdownCard, McCopyIcon } from '@matechat/core';
35
+ import { katex } from '@mdit/plugin-katex';
36
+ import { ChatMessage, ChatMessageChunk, ChatMessageToolChunk } from '../chat';
37
+ import { ChatService } from "../ChatService";
38
+ import ChatExecutor from './ChatExecutor.vue';
39
+
40
+ const chatService = runtime.get<ChatService>('chat')
41
+ const avatarSize = ref(36)
42
+ const wheelHadUp = ref(false);
43
+ const mdPlugins = ref([
44
+ { plugin: katex, opt: {} },
45
+ ])
46
+
47
+ const getTasks = (chunk: ChatMessageToolChunk) => {
48
+ const toolNames = [...chunk.collection.values()]
49
+ return toolNames
50
+ }
51
+
52
+ const getAvatarConfig = (msg: ChatMessage) => {
53
+ return {
54
+ imgSrc: msg.avatarConfig,
55
+ width: avatarSize.value,
56
+ height: avatarSize.value
57
+ }
58
+ }
59
+
60
+ const getTextContent = (msg: ChatMessage) => {
61
+ let content = ''
62
+ for (const chunk of msg.chunks) {
63
+ if (chunk.type === 'text') content += chunk.content
64
+ }
65
+ return content
66
+ }
67
+
68
+ const getRenderContent = (chunk: ChatMessageChunk) => {
69
+ if (chunk.type === 'text') {
70
+ return chunk.content
71
+ } else if (chunk.type === 'reasoning') {
72
+ return `<think>${chunk.content}</think>`
73
+ }
74
+ // else {
75
+ // let displayContent = `\n\n**🟡 ${chunk.toolName}**\n`;
76
+ // displayContent += `\n**${language.get('chat.process.tool.call.args')}**\n\`\`\`json\n${JSON.stringify(chunk.args, null, 2)}\n\`\`\`\n`;
77
+ // displayContent += `\n**${language.get('chat.process.tool.call.result')}**\n\`\`\`json\n${JSON.stringify(chunk.result, null, 2)}\n\`\`\`\n`;
78
+
79
+ // return displayContent
80
+ // }
81
+ }
82
+
83
+ const onWheel = (e: WheelEvent) => {
84
+ const wrapper = scrollRef?.value?.wrapper;
85
+ if (wrapper) {
86
+ wheelHadUp.value = e.deltaY < 0;
87
+ }
88
+ }
89
+
90
+ const scrollRef = useTemplateRef('scrollRef')
91
+ const nextTo = () => {
92
+ if (wheelHadUp.value) return
93
+ const wrapper = scrollRef?.value?.wrapper;
94
+ if (!wrapper) return
95
+ wrapper.scrollTo({
96
+ top: wrapper.scrollHeight,
97
+ behavior: 'smooth',
98
+ })
99
+ }
100
+ const scrollTo = () => nextTick(nextTo)
101
+ watch(() => chatService.manager.window.history.lastMessage.current, scrollTo, { deep: true })
102
+ </script>
103
+
104
+ <style scoped lang="scss">
105
+ @import "devui-theme/styles-var/devui-var.scss";
106
+ @import 'katex/dist/katex.min.css';
107
+
108
+ :deep(.mc-bubble-content-container) {
109
+ max-width: calc(100% - 78px) !important;
110
+
111
+ .mc-markdown-render {
112
+ overflow: hidden;
113
+ }
114
+ }
115
+
116
+ :deep(.mc-bubble-right) {
117
+ .bubble-bottom-operations {
118
+ text-align: right;
119
+ }
120
+ }
121
+
122
+ .tool-runing {
123
+ width: 100%;
124
+ height: 1.5em;
125
+ line-height: 1.5em;
126
+ display: flex;
127
+ align-items: center;
128
+
129
+ .tool-loading {
130
+ margin-left: 4px;
131
+ animation: loading-rotate 1.5s infinite;
132
+ }
133
+ }
134
+
135
+ @keyframes loading-rotate {
136
+ 0% {
137
+ transform: rotate(0);
138
+ }
139
+
140
+ 100% {
141
+ transform: rotate(360deg);
142
+ }
143
+ }
144
+ </style>
@@ -0,0 +1,135 @@
1
+ <template>
2
+ <div class="chat-view-wrapper" @mousedown="unactive">
3
+ <div class="chat-view-container">
4
+ <Welcome v-if="chatService.manager.window.history.list.length === 0" />
5
+ <ChatProcess class="chat-message" v-else />
6
+ <div class="new-convo-button">
7
+ <div class="agent-knowledge">
8
+ <ChatWindow class="chat-item" />
9
+ <span class="agent-knowledge-dividing-line"></span>
10
+ <ChatAgent class="chat-item" />
11
+ </div>
12
+ <div class="new-chat-btn" @click="addWindow">
13
+ <i class="vyrfont vyr-add"></i>
14
+ </div>
15
+ </div>
16
+ <Input />
17
+ </div>
18
+ </div>
19
+ </template>
20
+
21
+ <script setup lang="ts">
22
+ import { runtime, ShortcutkeyService } from "@vyr/runtime";
23
+ import { ChatService } from "../ChatService";
24
+ import ChatProcess from "./ChatProcess.vue";
25
+ import Input from "./Input.vue";
26
+ import Welcome from "./Welcome.vue";
27
+ import ChatAgent from './ChatAgent.vue'
28
+ import ChatWindow from './ChatWindow.vue'
29
+
30
+ const chatService = runtime.get<ChatService>('chat')
31
+ const shortcutkeyService = runtime.get<ShortcutkeyService>('shortcutkey')
32
+
33
+ const unactive = () => {
34
+ shortcutkeyService.unactive()
35
+ }
36
+
37
+ const addWindow = () => {
38
+ if (chatService.manager.isPendingWindow()) return
39
+ const window = chatService.manager.createWindow({ agentId: chatService.getDefaultAgentId() })
40
+ chatService.manager.setCurrentWindow(window)
41
+ }
42
+ </script>
43
+
44
+ <style scoped lang="scss">
45
+ @import "devui-theme/styles-var/devui-var.scss";
46
+
47
+ .chat-view-wrapper {
48
+ width: 100%;
49
+ height: 100%;
50
+ position: relative;
51
+ display: flex;
52
+ color: #fff;
53
+ }
54
+
55
+ .chat-view-container {
56
+ width: 100%;
57
+ height: 100%;
58
+ display: flex;
59
+ flex-direction: column;
60
+ align-items: center;
61
+ gap: 8px;
62
+
63
+ .chat-message {
64
+ flex: 1;
65
+ display: flex;
66
+ flex-direction: column;
67
+ overflow: auto;
68
+ width: 100%;
69
+ padding-top: 20px;
70
+ }
71
+
72
+ .chat-item {
73
+ border-radius: $devui-border-radius-full;
74
+
75
+ :deep(.vyr-dropdown-trigger) {
76
+ padding: 0 8px 0 4px;
77
+ }
78
+ }
79
+
80
+ .new-convo-button {
81
+ box-sizing: border-box;
82
+ padding: 0 12px;
83
+ display: flex;
84
+ justify-content: flex-end;
85
+ align-items: center;
86
+ width: 100%;
87
+ max-width: 1200px;
88
+ height: 39px;
89
+ gap: 4px;
90
+
91
+ .agent-knowledge {
92
+ flex: 1;
93
+ display: flex;
94
+ align-items: center;
95
+
96
+ .agent-knowledge-dividing-line {
97
+ width: 1px;
98
+ height: 14px;
99
+ margin: 0 12px;
100
+ background-color: $devui-line;
101
+ }
102
+ }
103
+ }
104
+
105
+ .new-chat-btn {
106
+ display: flex;
107
+ justify-content: center;
108
+ align-items: center;
109
+ width: 24px;
110
+ height: 24px;
111
+ border-radius: $devui-border-radius-full;
112
+ background-color: $devui-base-bg;
113
+ box-shadow: 0px 1px 8px 0px rgba(25, 25, 25, 0.06);
114
+ cursor: pointer;
115
+
116
+ &:hover {
117
+ color: $devui-brand;
118
+ }
119
+ }
120
+ }
121
+
122
+ body[ui-theme-type="light"] {
123
+ .chat-view-wrapper {
124
+ background: linear-gradient(180deg,
125
+ rgba(255, 255, 255, 0.95),
126
+ rgba(248, 250, 255, 0.95) 99%);
127
+ }
128
+ }
129
+
130
+ body[ui-theme-type="dark"] {
131
+ .chat-view-wrapper {
132
+ background-color: $devui-global-bg;
133
+ }
134
+ }
135
+ </style>
@@ -0,0 +1,64 @@
1
+ <template>
2
+ <vyr-dropdown class="window-wrapper" :model-value="chatService.manager.window.threadId" :data="windows"
3
+ :removable="true" @change="changeWindow" @remove="deleteWindow">
4
+ <template #default>
5
+ <div class="window">{{ chatService.manager.window.threadId }}</div>
6
+ </template>
7
+ </vyr-dropdown>
8
+ </template>
9
+
10
+ <script setup lang="ts">
11
+ import { computed } from 'vue';
12
+ import { runtime } from '@vyr/runtime';
13
+ import { Option, VyrDropdown } from '@vyr/design';
14
+ import { ChatService } from '../ChatService';
15
+
16
+ const chatService = runtime.get<ChatService>('chat')
17
+
18
+ const windows = computed(() => {
19
+ const items = chatService.manager.getWindows()
20
+ const windows: Option[] = []
21
+ for (const item of items) {
22
+ windows.push({ label: item.threadId, value: item.threadId })
23
+ }
24
+ return windows
25
+ })
26
+
27
+ const deleteWindow = async (index: number) => {
28
+ if (windows.value.length === 1) return
29
+ const item = windows.value[index]
30
+ const window = chatService.manager.getWindow(item.value)
31
+ if (window === null) return
32
+ await chatService.manager.deleteWindow(window)
33
+ if (chatService.manager.window === window) {
34
+ const current = chatService.manager.getWindows()[0]
35
+ chatService.manager.setCurrentWindow(current)
36
+ }
37
+ }
38
+ const changeWindow = (id: string) => {
39
+ const window = chatService.manager.getWindow(id)
40
+ if (window === null) return
41
+ chatService.manager.setCurrentWindow(window)
42
+ }
43
+ </script>
44
+
45
+ <style scoped lang="scss">
46
+ @import "devui-theme/styles-var/devui-var.scss";
47
+
48
+ $item-height: 24px;
49
+
50
+ .window-wrapper {
51
+ width: 140px;
52
+ background-color: $devui-base-bg;
53
+ box-shadow: 0px 1px 8px 0px rgba(25, 25, 25, 0.06);
54
+ cursor: pointer;
55
+
56
+ .window {
57
+ overflow: hidden;
58
+ height: $item-height;
59
+ line-height: $item-height;
60
+ padding: 0 0 0 8px;
61
+ box-sizing: border-box;
62
+ }
63
+ }
64
+ </style>
@@ -0,0 +1,170 @@
1
+ <template>
2
+ <div class="input-container">
3
+ <McInput :value="chatService.manager.window.question" :maxLength="chatService.manager.window.maxLength"
4
+ :variant="BorderLess" :loading="chatService.manager.window.runing" :disabled="chatService.manager.window.disbaled"
5
+ :placeholder="language.get('chat.input.placeholder')" @change="updateQuestion" @submit="onSubmit"
6
+ @mouseenter="mouseenter" @mouseup="mouseup" @cancel="onCancel">
7
+ <template #extra>
8
+ <div class="input-foot-wrapper">
9
+ <span class="input-foot-maxlength">
10
+ {{ chatService.manager.window.question.length }}/{{ chatService.manager.window.maxLength }}
11
+ </span>
12
+ </div>
13
+ </template>
14
+ </McInput>
15
+ </div>
16
+ </template>
17
+
18
+ <script setup lang="ts">
19
+ import { McInput } from '@matechat/core';
20
+ import { DataService, DraggableService, runtime } from '@vyr/runtime';
21
+ import { DraggableController } from '@vyr/design';
22
+ import { language } from '../locale';
23
+ import { ChatSession } from '../chat';
24
+ import { ChatService } from '../ChatService';
25
+
26
+ const dataService = runtime.get<DataService>('data')
27
+ const chatService = runtime.get<ChatService>('chat')
28
+ const BorderLess: any = 'borderless'
29
+
30
+ const updateQuestion = (value: string) => {
31
+ chatService.manager.window.question = value
32
+ }
33
+
34
+ let result: ChatSession | null = null
35
+
36
+ const onSubmit = async () => {
37
+ if (chatService.manager.window.runing) return
38
+ chatService.manager.window.runing = true
39
+ try {
40
+ result = await chatService.manager.window.ask(chatService.manager.window.question)
41
+ await result.done()
42
+ } catch (error) {
43
+ console.error(error)
44
+ } finally {
45
+ chatService.manager.window.runing = false
46
+ result = null
47
+ }
48
+ };
49
+
50
+ const onCancel = async () => {
51
+ if (result === null) return
52
+ try {
53
+ chatService.manager.window.disbaled = true
54
+ await result.stop()
55
+ } catch (error) {
56
+ console.log(error)
57
+ } finally {
58
+ chatService.manager.window.disbaled = false
59
+ }
60
+ }
61
+
62
+ const draggableService = runtime.get<DraggableService>('draggable')
63
+ const draggable = draggableService.get(DraggableService.key.chat)
64
+ const mouseenter = (e: MouseEvent) => {
65
+ if (DraggableController.enabled === false) return
66
+ DraggableController.enter({ key: '', value: null }, draggable)
67
+ }
68
+ const mouseup = (e: MouseEvent) => {
69
+ if (DraggableController.enabled === false) return
70
+ DraggableController.end({ key: '', value: dataService.sidebar.url }, draggable, 'insert')
71
+ }
72
+ </script>
73
+
74
+ <style scoped lang="scss">
75
+ @import "devui-theme/styles-var/devui-var.scss";
76
+
77
+ .input-container {
78
+ width: 100%;
79
+ max-width: 1200px;
80
+ padding: 0 12px 12px 12px;
81
+ box-sizing: border-box;
82
+
83
+ .input-foot-wrapper {
84
+ display: flex;
85
+ align-items: center;
86
+ width: 100%;
87
+ height: 100%;
88
+ margin-right: 8px;
89
+
90
+ .input-word-container {
91
+ display: flex;
92
+ align-items: center;
93
+ gap: 4px;
94
+ height: 30px;
95
+ color: $devui-text;
96
+ font-size: $devui-font-size;
97
+ border-radius: 4px;
98
+ padding: 6px;
99
+ cursor: pointer;
100
+
101
+ svg {
102
+ width: 14px;
103
+ height: 14px;
104
+ }
105
+
106
+ span {
107
+ font-size: $devui-font-size-sm;
108
+ }
109
+
110
+ &:hover {
111
+ background-color: var(--devui-icon-hover-bg);
112
+ }
113
+ }
114
+
115
+ span {
116
+ color: $devui-text;
117
+ cursor: pointer;
118
+ }
119
+
120
+ .input-foot-dividing-line {
121
+ width: 1px;
122
+ height: 14px;
123
+ background-color: $devui-line;
124
+ margin: 0 8px;
125
+ }
126
+
127
+ .input-foot-maxlength {
128
+ font-size: $devui-font-size-sm;
129
+ color: $devui-aide-text;
130
+ }
131
+ }
132
+
133
+ :deep() {
134
+ .mc-button svg path {
135
+ transition: fill $devui-animation-duration-slow $devui-animation-ease-in-out-smooth;
136
+ }
137
+ }
138
+
139
+ .statement-box {
140
+ font-size: 12px;
141
+ margin-top: 8px;
142
+ color: $devui-aide-text;
143
+ text-align: center;
144
+
145
+ .separator {
146
+ height: 12px;
147
+ margin: 0 4px;
148
+ border: 0.6px solid $devui-disabled-text;
149
+ }
150
+
151
+ .link-span {
152
+ cursor: pointer;
153
+ text-decoration: underline;
154
+ }
155
+ }
156
+ }
157
+
158
+ .input-container {
159
+ :deep() {
160
+ .mc-button:disabled {
161
+ color: $devui-disabled-text;
162
+ background-color: $devui-disabled-bg;
163
+
164
+ svg path {
165
+ fill: $devui-disabled-text;
166
+ }
167
+ }
168
+ }
169
+ }
170
+ </style>
@@ -0,0 +1,56 @@
1
+ <template>
2
+ <div class="welcome-page">
3
+ <div class="content-wrapper">
4
+ <McIntroduction :logo-img="'logo/透明图标.png'" :title="'VYR'" :sub-title="''" :description="[
5
+ language.get('chat.introduction.title'),
6
+ language.get('chat.introduction.message'),
7
+ ]" class="welcome-introduction">
8
+ </McIntroduction>
9
+ </div>
10
+ </div>
11
+ </template>
12
+
13
+ <script setup lang="ts">
14
+ import { McIntroduction } from '@matechat/core';
15
+ import { language } from '../locale';
16
+
17
+ </script>
18
+
19
+ <style scoped lang="scss">
20
+ @import "devui-theme/styles-var/devui-var.scss";
21
+
22
+ .welcome-page {
23
+ flex: 1;
24
+ display: flex;
25
+ flex-direction: column;
26
+ justify-content: flex-start;
27
+ box-sizing: border-box;
28
+
29
+ .content-wrapper {
30
+ margin: auto 0;
31
+ width: 100%;
32
+ gap: 24px;
33
+ display: flex;
34
+ flex-direction: column;
35
+ min-height: 0;
36
+ }
37
+
38
+ overflow: auto;
39
+ width: 100%;
40
+ max-width: 1200px;
41
+ padding: 0 12px;
42
+ gap: 24px;
43
+
44
+ .welcome-introduction {
45
+ :deep() {
46
+ .mc-introduction-description {
47
+ font-size: var(--devui-font-size, 14px);
48
+
49
+ div {
50
+ line-height: 2;
51
+ }
52
+ }
53
+ }
54
+ }
55
+ }
56
+ </style>