@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.
package/package.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "@vyr/service-chat",
3
+ "version": "0.0.34",
4
+ "description": "",
5
+ "main": "src/index.ts",
6
+ "keywords": [],
7
+ "author": "",
8
+ "license": "ISC",
9
+ "dependencies": {
10
+ "@matechat/core": "^1.5.2",
11
+ "@mdit/plugin-katex": "^0.24.2",
12
+ "devui-theme": "^0.1.0",
13
+ "mermaid": "^11.12.2"
14
+ },
15
+ "devDependencies": {
16
+ "sass": "^1.32.0"
17
+ }
18
+ }
@@ -0,0 +1,104 @@
1
+ import z from 'zod'
2
+ import { reactive } from "vue";
3
+ import { MastraClient } from "@mastra/client-js";
4
+ import { AsyncTask } from "@vyr/engine";
5
+ import { Option } from "@vyr/design";
6
+ import { Service } from "@vyr/service";
7
+ import { DraggableService, runtime } from "@vyr/runtime";
8
+ import { api, request, ChatWindow as _Window, GatewayService, } from "@vyr/service-gateway"
9
+ import { ChatSession, ChatWindowManager } from "./chat"
10
+ import { Draggable } from "./common";
11
+ import { bindExecutor } from "./executor";
12
+
13
+ class ChatService extends Service {
14
+ private static _client: MastraClient | null = null
15
+ static get client() {
16
+ if (this._client === null) {
17
+ const location = new URL(window.location.href)
18
+ location.pathname = api.chat.prefix.path
19
+ this._client = new MastraClient({ baseUrl: location.origin + location.pathname, retries: 0, fetch: ChatSession.fetch })
20
+ }
21
+ return this._client
22
+ }
23
+ readonly initializeTask: AsyncTask
24
+ readonly manager = reactive<ChatWindowManager>(new ChatWindowManager())
25
+ agents: Option[] = []
26
+
27
+ constructor(name: string) {
28
+ super(name)
29
+ this.initializeTask = new AsyncTask(async () => {
30
+ await this.manager.initialize()
31
+ await this.initialize()
32
+ })
33
+ }
34
+
35
+ async initialize() {
36
+ const collection = await ChatService.client.listAgents()
37
+ const keys = Object.keys(collection)
38
+ for (const key of keys) {
39
+ this.agents.push({ label: key, value: collection[key].id })
40
+ }
41
+ const agent = this.agents[0]
42
+ if (agent) this.manager.window.agentId = agent.value
43
+ }
44
+
45
+ async setup() {
46
+ const draggableService = runtime.get<DraggableService>('draggable')
47
+ const draggable = new Draggable()
48
+ draggableService.set(draggable.id, draggable)
49
+ }
50
+
51
+ async ready() {
52
+ const gatewayService = runtime.get<GatewayService>('gateway')
53
+ bindExecutor(gatewayService)
54
+ }
55
+
56
+ async start() {
57
+ await this.initializeTask.done()
58
+ }
59
+
60
+ getDefaultAgentId() {
61
+ return this.agents[0]?.value ?? ''
62
+ }
63
+
64
+
65
+ async list(data: z.infer<typeof api.chat.list.requestSchema>) {
66
+ const res = await request<{ list: _Window[] }>({
67
+ url: api.chat.list.path,
68
+ method: api.chat.list.method,
69
+ data,
70
+ })
71
+
72
+ return res.data
73
+ }
74
+
75
+ async create(data: z.infer<typeof api.chat.create.requestSchema>) {
76
+ await request<{ list: _Window[] }>({
77
+ url: api.chat.create.path,
78
+ method: api.chat.create.method,
79
+ data,
80
+ })
81
+ }
82
+
83
+ async delete(data: z.infer<typeof api.chat.delete.requestSchema>) {
84
+ await request({
85
+ url: api.chat.delete.path,
86
+ method: api.chat.delete.method,
87
+ data,
88
+ })
89
+ }
90
+
91
+ async stop(data: z.infer<typeof api.chat.stop.requestSchema>) {
92
+ const res = await request<{ success: boolean }>({
93
+ url: api.chat.stop.path,
94
+ method: api.chat.stop.method,
95
+ data,
96
+ })
97
+
98
+ return res.data
99
+ }
100
+ }
101
+
102
+ export {
103
+ ChatService
104
+ }
@@ -0,0 +1,127 @@
1
+ import { MastraDBMessage } from "@mastra/core/agent";
2
+ import { TaskItem } from "../components/executor/types";
3
+ import { ChatWindow } from "./ChatWindow";
4
+
5
+ interface ChatMessageTextChunk {
6
+ type: 'text'
7
+ content: string
8
+ }
9
+
10
+ interface ChatMessageReasoningChunk {
11
+ type: 'reasoning'
12
+ content: string
13
+ }
14
+
15
+ interface ChatMessageToolChunk {
16
+ type: 'tool'
17
+ collection: Map<string, TaskItem>
18
+ }
19
+
20
+ type ChatMessageChunk = ChatMessageTextChunk | ChatMessageReasoningChunk | ChatMessageToolChunk
21
+
22
+ interface ChatMessageTool {
23
+ toolName: string
24
+ args: any
25
+ }
26
+
27
+ class ChatMessage {
28
+ id: string
29
+ role: 'user' | 'assistant' | 'system';
30
+ chunks: ChatMessageChunk[]
31
+ avatarPosition: 'side-left' | 'side-right';
32
+ avatarConfig?: string
33
+ loading?: boolean
34
+ complete?: boolean;
35
+ current?: ChatMessageChunk
36
+
37
+ constructor(msg: Partial<ChatMessage> = {}) {
38
+ this.id = msg.id ?? ''
39
+ this.role = msg.role ?? 'assistant'
40
+ this.chunks = msg.chunks ?? []
41
+ this.avatarPosition = `side-${this.role === 'user' ? 'right' : 'left'}`
42
+ this.avatarConfig = `logo/${this.role === 'user' ? 'chat' : '透明图标'}.png`
43
+ this.loading = msg.loading ?? false
44
+ this.complete = msg.complete ?? true
45
+ }
46
+
47
+ add(chunk: ChatMessageChunk) {
48
+ this.chunks.push(chunk)
49
+ this.current = chunk
50
+ }
51
+
52
+ startNewChunk(type: ChatMessageChunk['type'], initialContent = '') {
53
+ let currentChunk: ChatMessageChunk
54
+ if (type === 'tool') {
55
+ currentChunk = { type, collection: new Map }
56
+ } else {
57
+ currentChunk = { type, content: initialContent }
58
+ }
59
+ this.add(currentChunk)
60
+ return currentChunk
61
+ }
62
+
63
+ startToolChunk(): ChatMessageToolChunk {
64
+ const chunk = this.chunks[this.chunks.length - 1]
65
+ if (chunk === undefined || chunk.type !== 'tool') {
66
+ return this.startNewChunk('tool') as ChatMessageToolChunk
67
+ } else {
68
+ return chunk
69
+ }
70
+ }
71
+
72
+ addFromParts(parts: { type: string, [k: string]: any }[]) {
73
+ for (const part of parts) {
74
+ if (part.type === 'text') {
75
+ if (part.text.length === 0) continue
76
+ this.add({ type: 'text', content: part.text })
77
+ } else if (part.type === 'reasoning') {
78
+ if (part.reasoning.length === 0) continue
79
+ this.add({ type: 'reasoning', content: part.reasoning })
80
+ } else if (part.type === 'tool-invocation') {
81
+ const tool = part.toolInvocation as any
82
+ const chunk = this.startToolChunk()
83
+ chunk.collection.set(tool.toolCallId, {
84
+ ...tool,
85
+ state: tool.result?.error ? 'failed' : 'success'
86
+ })
87
+ }
88
+ }
89
+ }
90
+ }
91
+
92
+ class ChatHistory {
93
+ private messages: ChatMessage[] = []
94
+ get list() {
95
+ return [...this.messages]
96
+ }
97
+ lastMessage!: ChatMessage
98
+
99
+ addMessage(msg: ChatMessage) {
100
+ this.messages.push(msg)
101
+ this.lastMessage = msg
102
+ }
103
+
104
+ addMessageFromRecord(record: MastraDBMessage) {
105
+ const chatMsg = new ChatMessage({ role: record.role })
106
+ chatMsg.addFromParts(record.content.parts)
107
+ this.addMessage(chatMsg)
108
+ }
109
+
110
+ async restore(window: ChatWindow) {
111
+ try {
112
+ const thread = await window.getThread()
113
+ const result = await thread.listMessages({ resourceId: window.threadId })
114
+ for (const record of result.messages) this.addMessageFromRecord(record)
115
+ } catch (error) {
116
+ console.log(error)
117
+ }
118
+ }
119
+ }
120
+
121
+ export {
122
+ ChatMessageToolChunk,
123
+ ChatMessageChunk,
124
+ ChatMessageTool,
125
+ ChatMessage,
126
+ ChatHistory,
127
+ }
@@ -0,0 +1,183 @@
1
+ import z from 'zod'
2
+ import { MastraMessagePart } from "@mastra/core/agent/message-list"
3
+ import { RequestContext } from "@mastra/core/request-context"
4
+ import { AsyncTask, Generate } from "@vyr/engine"
5
+ import { runtime } from "@vyr/runtime"
6
+ import { api, GatewayService, setAuthorization, setClient } from "@vyr/service-gateway"
7
+ import { UserService } from "@vyr/service-user"
8
+ import { language } from "../locale"
9
+ import { TaskItem } from "../components/executor/types"
10
+ import { ChatMessage, ChatMessageChunk } from "./ChatHistory"
11
+ import { ChatWindow } from "./ChatWindow"
12
+ import { ChatService } from "../ChatService"
13
+
14
+ class ChatSession {
15
+ static fetch = async (input: RequestInfo | URL, init?: RequestInit) => {
16
+ const modifiedInit = { ...init }
17
+ if (modifiedInit.headers === undefined) modifiedInit.headers = {}
18
+ setAuthorization(modifiedInit.headers, UserService.getToken())
19
+ setClient(modifiedInit.headers)
20
+
21
+ return fetch(input, modifiedInit)
22
+ }
23
+ private executor
24
+ readonly runId = Generate.uuid()
25
+ readonly window
26
+
27
+ constructor(window: ChatWindow) {
28
+ this.window = window
29
+ this.executor = new AsyncTask(this.start)
30
+ }
31
+
32
+ private onChunk = async (chunk: any) => {
33
+ if (chunk.type === 'start') {
34
+ this.window.history.lastMessage.id = chunk?.payload?.messageId ?? ''
35
+ }
36
+
37
+ if (chunk.type === 'text-start' || chunk.type === 'reasoning-start') {
38
+ this.window.history.lastMessage.loading = false
39
+ }
40
+
41
+ if (chunk.type === 'tool-call') {
42
+ this.window.history.lastMessage.loading = false
43
+ const payload = chunk.payload ?? {}
44
+ const toolChunk = this.window.history.lastMessage.startToolChunk()
45
+ toolChunk.collection.set(payload.toolCallId, {
46
+ ...payload,
47
+ state: 'running',
48
+ })
49
+ }
50
+
51
+ if (chunk.type === 'tool-result') {
52
+ const payload = chunk.payload || {};
53
+ const toolChunk = this.window.history.lastMessage.startToolChunk()
54
+ const task = toolChunk.collection.get(payload.toolCallId) as TaskItem
55
+ task.state = payload.result?.error ? 'failed' : 'success'
56
+ task.result = payload.result
57
+ return
58
+ }
59
+
60
+ if (chunk.type === 'text-delta' && chunk.payload?.text) {
61
+ if (!this.window.history.lastMessage.current || this.window.history.lastMessage.current.type !== 'text') {
62
+ this.window.history.lastMessage.startNewChunk('text', chunk.payload.text)
63
+ } else {
64
+ this.window.history.lastMessage.current.content += chunk.payload.text
65
+ }
66
+ return
67
+ }
68
+
69
+ if (chunk.type === 'reasoning-delta' && chunk.payload?.text) {
70
+ if (!this.window.history.lastMessage.current || this.window.history.lastMessage.current.type !== 'reasoning') {
71
+ this.window.history.lastMessage.startNewChunk('reasoning', chunk.payload.text)
72
+ } else {
73
+ this.window.history.lastMessage.current.content += chunk.payload.text;
74
+ }
75
+ return
76
+ }
77
+
78
+ // 成功完成
79
+ if (chunk.type === 'finish') {
80
+ return
81
+ }
82
+
83
+ // 异常终止
84
+ if (chunk.type === 'error') {
85
+ return;
86
+ }
87
+
88
+ console.log('未处理的数据块类型:', chunk.type)
89
+ }
90
+
91
+ private start = async (question: string) => {
92
+ this.window.history.addMessage(new ChatMessage({ role: 'user', chunks: [{ type: 'text', content: question }] }))
93
+ this.window.history.addMessage(new ChatMessage({ role: 'assistant', complete: false, loading: true }))
94
+
95
+ const requestContext = new RequestContext()
96
+ requestContext.set('client', GatewayService.client)
97
+
98
+ const response = await this.window.getAgent().stream(question, {
99
+ runId: this.runId,
100
+ memory: { thread: this.window.threadId, resource: this.window.threadId },
101
+ requestContext
102
+ })
103
+ await response.processDataStream({ onChunk: this.onChunk })
104
+
105
+ this.window.history.lastMessage.loading = false
106
+ this.window.history.lastMessage.complete = true
107
+ }
108
+
109
+ run(question: string) {
110
+ return this.executor.run(question)
111
+ }
112
+
113
+ done() {
114
+ return this.executor.done()
115
+ }
116
+
117
+ async closeRequest() {
118
+ const chatService = runtime.get<ChatService>('chat')
119
+
120
+ const data: z.infer<typeof api.chat.stop.requestSchema> = {
121
+ runId: this.runId
122
+ }
123
+ return await chatService.stop(data)
124
+ }
125
+
126
+ async shouldSaveMessage() {
127
+ if (this.window.history.lastMessage.id) {
128
+ const thread = await this.window.getThread()
129
+ const result = await thread.listMessages({ page: 0, perPage: 1, orderBy: { field: 'createdAt', direction: 'DESC' } })
130
+ const lastId = result.messages[0]?.id
131
+ if (lastId === this.window.history.lastMessage.id) return false
132
+ }
133
+
134
+ return true
135
+ }
136
+
137
+ async saveMessage() {
138
+ const chunk: ChatMessageChunk = { type: 'text', content: language.get('chat.user.stop') }
139
+ this.window.history.lastMessage.add(chunk)
140
+ let content = ''
141
+ const parts: MastraMessagePart[] = []
142
+ const toolInvocations: any[] = []
143
+ for (const chunk of this.window.history.lastMessage.chunks) {
144
+ if (chunk.type === 'text') {
145
+ content += chunk.content
146
+ parts.push({ type: 'text', text: chunk.content })
147
+ } else if (chunk.type === 'tool') {
148
+ const toolInvocation = { ...chunk, type: undefined, state: 'result' }
149
+ toolInvocations.push(toolInvocation)
150
+ parts.push({ type: 'tool-invocation', toolInvocation })
151
+ }
152
+ }
153
+
154
+ const agentId = this.window.agentId
155
+ await ChatService.client.saveMessageToMemory({
156
+ messages: [
157
+ {
158
+ id: this.window.history.lastMessage.id,
159
+ type: 'text',
160
+ createdAt: new Date(),
161
+ threadId: this.window.threadId,
162
+ resourceId: this.window.threadId,
163
+ role: 'assistant',
164
+ content: { format: 2, content, parts, toolInvocations },
165
+ }
166
+ ],
167
+ agentId,
168
+ })
169
+ }
170
+
171
+ async stop() {
172
+ const data = await this.closeRequest()
173
+ this.window.history.lastMessage.loading = false
174
+ if (data.success === false) return
175
+ const isSave = await this.shouldSaveMessage()
176
+ if (isSave === false) return
177
+ await this.saveMessage()
178
+ }
179
+ }
180
+
181
+ export {
182
+ ChatSession
183
+ }
@@ -0,0 +1,92 @@
1
+ import z from 'zod'
2
+ import { MemoryThread } from "@mastra/client-js/dist/resources";
3
+ import { Generate } from "@vyr/engine";
4
+ import { runtime } from "@vyr/runtime";
5
+ import { ChatWindow as _Window, api } from "@vyr/service-gateway";
6
+ import { ChatHistory } from "./ChatHistory";
7
+ import { ChatSession } from "./ChatSession";
8
+ import { ChatService } from "../ChatService";
9
+
10
+ class ChatWindow {
11
+ private _needCreate = true
12
+ get completed() {
13
+ return this._needCreate === false
14
+ }
15
+ private _thread: MemoryThread | null = null
16
+ readonly history
17
+ readonly threadId
18
+ readonly title
19
+ agentId = ''
20
+ runing = false
21
+ disbaled = false
22
+ maxLength = 2000
23
+ question = ''
24
+
25
+ constructor(options: Partial<_Window> = {}) {
26
+ this.history = new ChatHistory()
27
+ this.threadId = options.id ?? Generate.uuid()
28
+ this.title = options.title ?? this.threadId
29
+ this.agentId = options.agentId ?? ''
30
+ }
31
+
32
+ private async tryCreate() {
33
+ const chatService = runtime.get<ChatService>('chat')
34
+ const listData: z.infer<typeof api.chat.list.requestSchema> = {
35
+ id: this.threadId,
36
+ }
37
+ const data = await chatService.list(listData)
38
+ if (data.list.length > 0) {
39
+ this._needCreate = false
40
+ return
41
+ }
42
+
43
+ const createData: z.infer<typeof api.chat.create.requestSchema> = {
44
+ window: {
45
+ id: this.threadId,
46
+ title: this.title,
47
+ agentId: this.agentId,
48
+ }
49
+ }
50
+ await chatService.create(createData)
51
+ this._needCreate = false
52
+ }
53
+
54
+ setNeedCreate(create: boolean) {
55
+ this._needCreate = create
56
+ }
57
+
58
+ async getThread() {
59
+ if (this._thread === null) {
60
+ try {
61
+ this._thread = ChatService.client.getMemoryThread({ threadId: this.threadId, agentId: this.agentId })
62
+ } catch (error) {
63
+ await ChatService.client.createMemoryThread({
64
+ threadId: this.threadId,
65
+ resourceId: this.threadId,
66
+ agentId: this.agentId
67
+ })
68
+ this._thread = ChatService.client.getMemoryThread({
69
+ threadId: this.threadId,
70
+ agentId: this.agentId
71
+ })
72
+ }
73
+ }
74
+
75
+ return this._thread
76
+ }
77
+
78
+ getAgent() {
79
+ return ChatService.client.getAgent(this.agentId)
80
+ }
81
+
82
+ async ask(question: string) {
83
+ if (this._needCreate) await this.tryCreate()
84
+ const chatSession = new ChatSession(this)
85
+ chatSession.run(question)
86
+ return chatSession
87
+ }
88
+ }
89
+
90
+ export {
91
+ ChatWindow,
92
+ }
@@ -0,0 +1,72 @@
1
+ import z from 'zod'
2
+ import { api, ChatWindow as _Window } from "@vyr/service-gateway";
3
+ import { ChatWindow } from "./ChatWindow";
4
+ import { runtime } from "@vyr/runtime";
5
+ import { ChatService } from "../ChatService";
6
+
7
+ class ChatWindowManager {
8
+ private collection = new Map<string, ChatWindow>()
9
+ window = this.createWindow()
10
+
11
+ createWindow(options: Partial<_Window> = {}) {
12
+ const window = new ChatWindow(options)
13
+ this.collection.set(window.threadId, window)
14
+ return window
15
+ }
16
+
17
+ async deleteWindow(window: ChatWindow) {
18
+ if (window.completed) {
19
+ const thread = await window.getThread()
20
+ await thread.delete()
21
+ const chatService = runtime.get<ChatService>('chat')
22
+
23
+ const data: z.infer<typeof api.chat.delete.requestSchema> = {
24
+ id: window.threadId,
25
+ }
26
+ await chatService.delete(data)
27
+ }
28
+ this.collection.delete(window.threadId)
29
+ }
30
+
31
+ setCurrentWindow(window: ChatWindow) {
32
+ this.window = window
33
+ }
34
+
35
+ getWindow(id: string) {
36
+ return this.collection.get(id) ?? null
37
+ }
38
+
39
+ getWindows() {
40
+ return [...this.collection.values()]
41
+ }
42
+
43
+ isPendingWindow() {
44
+ const windows = this.collection.values()
45
+ for (const window of windows) {
46
+ if (window.completed === false) return true
47
+ }
48
+
49
+ return false
50
+ }
51
+
52
+ async initialize() {
53
+ const chatService = runtime.get<ChatService>('chat')
54
+
55
+ const listData: z.infer<typeof api.chat.list.requestSchema> = {}
56
+ const data = await chatService.list(listData)
57
+ const first = data?.list[0]
58
+ if (first === undefined) return
59
+ this.collection.clear()
60
+ for await (const item of data.list) {
61
+ const window = this.createWindow(item)
62
+ window.setNeedCreate(false)
63
+ await window.history.restore(window)
64
+ }
65
+ const window = this.collection.get(first.id) as ChatWindow
66
+ this.setCurrentWindow(window)
67
+ }
68
+ }
69
+
70
+ export {
71
+ ChatWindowManager
72
+ }
@@ -0,0 +1,4 @@
1
+ export * from './ChatSession'
2
+ export * from './ChatHistory'
3
+ export * from './ChatWindow'
4
+ export * from './ChatWindowManager'
@@ -0,0 +1,36 @@
1
+ import { Descriptor } from "@vyr/engine"
2
+ import { Draggable as _Draggable, DraggableService, runtime } from "@vyr/runtime"
3
+ import { DraggableData, DraggableEndType, } from '@vyr/declare'
4
+ import { Prefab, VirtualNode } from "@vyr/service-gateway"
5
+ import { language } from '../locale'
6
+ import { ChatService } from "../ChatService"
7
+
8
+ class Draggable extends _Draggable<VirtualNode> {
9
+ readonly id = DraggableService.key.chat
10
+
11
+ starter(dragData: VirtualNode) {
12
+ return false
13
+ }
14
+
15
+ validator(dragData: DraggableData<VirtualNode>, targetData: DraggableData<VirtualNode>) {
16
+ return true
17
+ }
18
+
19
+ finished(dragData: DraggableData<VirtualNode | Descriptor | Prefab>, targetData: DraggableData, type: DraggableEndType) {
20
+ const chatService = runtime.get<ChatService>('chat')
21
+
22
+ let content = ''
23
+ if (dragData.id === DraggableService.key.footer && dragData.data instanceof VirtualNode) {
24
+ content = language.get('chat.input.format.content.asset', { url: dragData.data.url })
25
+ } else if (dragData.id === DraggableService.key.asset && dragData.data instanceof Descriptor) {
26
+ content = language.get('chat.input.format.content.node', { uuid: dragData.data.uuid, url: targetData.data.value })
27
+ } else if (dragData.id === DraggableService.key.prefab) {
28
+ const prefab = dragData.data as unknown as Prefab
29
+ content = language.get('chat.input.format.content.prefab', { id: prefab.id })
30
+ }
31
+
32
+ chatService.manager.window.question += content
33
+ }
34
+ }
35
+
36
+ export { Draggable }
@@ -0,0 +1 @@
1
+ export * from './Draggable'
@@ -0,0 +1,44 @@
1
+ <template>
2
+ <vyr-dropdown class="agent-wrapper" :data="chatService.agents" :model-value="chatService.manager.window.agentId">
3
+ <div class="agent">
4
+ {{ agentName }}
5
+ </div>
6
+ </vyr-dropdown>
7
+ </template>
8
+
9
+ <script setup lang="ts">
10
+ import { computed } from 'vue';
11
+ import { runtime } from '@vyr/runtime';
12
+ import { VyrDropdown } from '@vyr/design';
13
+ import { ChatService } from '../ChatService';
14
+
15
+ const chatService = runtime.get<ChatService>('chat')
16
+
17
+ const agentName = computed(() => {
18
+ for (const agent of chatService.agents) {
19
+ if (agent.value === chatService.manager.window.agentId) return agent.label
20
+ }
21
+ return ' - '
22
+ })
23
+ </script>
24
+
25
+ <style scoped lang="scss">
26
+ @import "devui-theme/styles-var/devui-var.scss";
27
+
28
+ $item-height: 24px;
29
+
30
+ .agent-wrapper {
31
+ width: 120px;
32
+ background-color: $devui-base-bg;
33
+ box-shadow: 0px 1px 8px 0px rgba(25, 25, 25, 0.06);
34
+ cursor: pointer;
35
+
36
+ .agent {
37
+ overflow: hidden;
38
+ height: $item-height;
39
+ line-height: $item-height;
40
+ padding: 0 0 0 8px;
41
+ box-sizing: border-box;
42
+ }
43
+ }
44
+ </style>