@opentiny/tiny-robot-cli 0.4.2-alpha.0

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.
Files changed (36) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +21 -0
  3. package/bin/cli.js +187 -0
  4. package/package.json +37 -0
  5. package/templates/basic/.env.example +2 -0
  6. package/templates/basic/README.md +45 -0
  7. package/templates/basic/index.html +13 -0
  8. package/templates/basic/package.json +29 -0
  9. package/templates/basic/public/favicon.ico +0 -0
  10. package/templates/basic/public/modelcontextprotocol.png +0 -0
  11. package/templates/basic/src/App.vue +130 -0
  12. package/templates/basic/src/components/ChatList.vue +82 -0
  13. package/templates/basic/src/components/ChatSender.vue +125 -0
  14. package/templates/basic/src/components/ConversationHistory.vue +136 -0
  15. package/templates/basic/src/components/HistoryDrawerButton.vue +43 -0
  16. package/templates/basic/src/components/McpServerPickerButton.vue +278 -0
  17. package/templates/basic/src/components/ThemeToggleButton.vue +44 -0
  18. package/templates/basic/src/components/icons/IconDeepThink.vue +29 -0
  19. package/templates/basic/src/components/icons/IconModelAliyunBailian.vue +51 -0
  20. package/templates/basic/src/components/icons/IconModelDeepseek.vue +29 -0
  21. package/templates/basic/src/components/icons/IconMoon.vue +29 -0
  22. package/templates/basic/src/components/icons/IconPlugin.vue +29 -0
  23. package/templates/basic/src/components/icons/IconSun.vue +35 -0
  24. package/templates/basic/src/components/icons/IconWebSearch.vue +36 -0
  25. package/templates/basic/src/components/icons/index.ts +7 -0
  26. package/templates/basic/src/composables/useChat.ts +129 -0
  27. package/templates/basic/src/composables/useMcp.ts +170 -0
  28. package/templates/basic/src/composables/useModel.ts +82 -0
  29. package/templates/basic/src/main.ts +7 -0
  30. package/templates/basic/src/mcpServers.ts +40 -0
  31. package/templates/basic/src/models.ts +81 -0
  32. package/templates/basic/src/style.css +21 -0
  33. package/templates/basic/tsconfig.app.json +16 -0
  34. package/templates/basic/tsconfig.json +7 -0
  35. package/templates/basic/tsconfig.node.json +26 -0
  36. package/templates/basic/vite.config.ts +16 -0
@@ -0,0 +1,125 @@
1
+ <template>
2
+ <tr-sender
3
+ v-model="inputMessage"
4
+ mode="multiple"
5
+ :placeholder="isProcessing ? '思考中...' : '请输入你的问题...'"
6
+ :clearable="true"
7
+ :loading="isProcessing"
8
+ :show-word-limit="true"
9
+ :max-length="1000"
10
+ @submit="sendMessage"
11
+ @cancel="abortActiveRequest"
12
+ >
13
+ <template #footer>
14
+ <div class="model-actions">
15
+ <button
16
+ class="sender-action-btn sender-capability-btn"
17
+ :class="{ 'sender-capability-btn--active': thinkingEnabled }"
18
+ type="button"
19
+ :disabled="!supportsThinking"
20
+ @click="thinkingEnabled = !thinkingEnabled"
21
+ >
22
+ <IconDeepThink :size="16" class="sender-action-btn__icon" />
23
+ 深度思考
24
+ </button>
25
+ <button
26
+ class="sender-action-btn sender-capability-btn"
27
+ :class="{ 'sender-capability-btn--active': searchEnabled }"
28
+ type="button"
29
+ :disabled="!supportsSearch"
30
+ @click="searchEnabled = !searchEnabled"
31
+ >
32
+ <IconWebSearch :size="16" class="sender-action-btn__icon" />
33
+ 联网搜索
34
+ </button>
35
+ <McpServerPickerButton />
36
+ <tr-dropdown-menu :items="modelMenuItems" trigger="click" @item-click="handleModelSelect">
37
+ <template #trigger>
38
+ <button class="sender-action-btn sender-model-btn" type="button">
39
+ <component :is="selectedModel?.icon" :size="16" class="sender-action-btn__icon" />
40
+ <span>{{ selectedModel?.name || '选择模型' }}</span>
41
+ </button>
42
+ </template>
43
+ </tr-dropdown-menu>
44
+ </div>
45
+ </template>
46
+ </tr-sender>
47
+ </template>
48
+
49
+ <script setup lang="ts">
50
+ import { TrDropdownMenu, TrSender } from '@opentiny/tiny-robot'
51
+ import { computed } from 'vue'
52
+ import { useChat } from '../composables/useChat'
53
+ import { useModel } from '../composables/useModel'
54
+ import McpServerPickerButton from './McpServerPickerButton.vue'
55
+ import { IconDeepThink, IconWebSearch } from './icons'
56
+
57
+ const { inputMessage, isProcessing, sendMessage, abortActiveRequest } = useChat()
58
+ const {
59
+ modelOptions,
60
+ selectedModel,
61
+ selectedModelId,
62
+ thinkingEnabled,
63
+ searchEnabled,
64
+ supportsThinking,
65
+ supportsSearch,
66
+ } = useModel()
67
+
68
+ const modelMenuItems = computed(() =>
69
+ modelOptions.map((item) => ({
70
+ id: item.id,
71
+ text: item.name,
72
+ })),
73
+ )
74
+
75
+ function handleModelSelect(item: { id?: string }) {
76
+ if (!item.id) {
77
+ return
78
+ }
79
+ selectedModelId.value = item.id
80
+ }
81
+ </script>
82
+
83
+ <style scoped>
84
+ .model-actions {
85
+ display: flex;
86
+ align-items: center;
87
+ gap: 8px;
88
+ }
89
+
90
+ .sender-action-btn {
91
+ border: 1px solid var(--tr-border-color-disabled);
92
+ border-radius: var(--tr-radius-full);
93
+ background: var(--tr-container-bg-default);
94
+ color: var(--tr-text-secondary);
95
+ font-size: var(--tr-font-size-sm);
96
+ height: 32px;
97
+ padding: 0 10px;
98
+ line-height: 1;
99
+ cursor: pointer;
100
+ display: inline-flex;
101
+ align-items: center;
102
+ gap: 6px;
103
+ }
104
+
105
+ .sender-action-btn__icon {
106
+ flex-shrink: 0;
107
+ }
108
+
109
+ .sender-action-btn:hover:not(:disabled) {
110
+ border-color: var(--tr-border-color-hover);
111
+ color: var(--tr-text-primary);
112
+ background: var(--tr-container-bg-hover);
113
+ }
114
+
115
+ .sender-action-btn:disabled {
116
+ cursor: not-allowed;
117
+ opacity: 0.5;
118
+ }
119
+
120
+ .sender-capability-btn--active {
121
+ border-color: var(--tr-border-color-hover);
122
+ color: var(--tr-text-primary);
123
+ background: var(--tr-container-bg-default-2);
124
+ }
125
+ </style>
@@ -0,0 +1,136 @@
1
+ <template>
2
+ <div v-if="historyDrawerOpen" class="history-drawer-backdrop" @click="historyDrawerOpen = false"></div>
3
+ <aside class="history-panel" :class="{ 'history-panel--open': historyDrawerOpen }">
4
+ <div class="history-panel__header">
5
+ <h2>历史会话</h2>
6
+ <button class="new-chat-btn" type="button" @click="activeConversationId = null">新会话</button>
7
+ </div>
8
+ <tr-history
9
+ class="history-panel__list"
10
+ :data="historyData"
11
+ :selected="activeConversationId ?? undefined"
12
+ @item-click="handleItemClick"
13
+ @item-title-change="handleItemTitleChange"
14
+ @item-action="handleItemAction"
15
+ ></tr-history>
16
+ </aside>
17
+ </template>
18
+
19
+ <script setup lang="ts">
20
+ import { TrHistory } from '@opentiny/tiny-robot'
21
+ import { inject, ref, type Ref, watch } from 'vue'
22
+ import { useChat } from '../composables/useChat'
23
+
24
+ type HistoryAction = {
25
+ id: string
26
+ }
27
+
28
+ const { activeConversationId, switchConversation, updateConversationTitle, deleteConversation, conversations } =
29
+ useChat()
30
+ const historyDrawerOpen = inject<Ref<boolean>>('historyDrawerOpen', ref(false))
31
+
32
+ const historyData = ref<Array<{ id: string; title: string }>>([])
33
+
34
+ watch(
35
+ () => conversations.value.length,
36
+ () => {
37
+ const list = conversations.value
38
+ historyData.value = list.map((item) => ({
39
+ id: item.id,
40
+ title: item.title || 'New Chat',
41
+ }))
42
+ },
43
+ { immediate: true },
44
+ )
45
+
46
+ function handleItemClick(item: { id: string }) {
47
+ switchConversation(item.id)
48
+ historyDrawerOpen.value = false
49
+ }
50
+
51
+ function handleItemTitleChange(newTitle: string, item: { id: string }) {
52
+ // Keep local list writable for TrHistory inline editing.
53
+ const target = historyData.value.find((historyItem) => historyItem.id === item.id)
54
+ if (target) {
55
+ target.title = newTitle
56
+ }
57
+ updateConversationTitle(item.id, newTitle)
58
+ }
59
+
60
+ function handleItemAction(action: HistoryAction, item: { id: string }) {
61
+ if (action.id === 'delete') {
62
+ deleteConversation(item.id)
63
+ }
64
+ }
65
+ </script>
66
+
67
+ <style scoped>
68
+ .history-panel {
69
+ width: 300px;
70
+ flex-shrink: 0;
71
+ display: flex;
72
+ flex-direction: column;
73
+ border-right: 1px solid var(--tr-border-color-disabled);
74
+ }
75
+
76
+ .history-panel__header {
77
+ padding: 12px 24px 0 24px;
78
+ display: flex;
79
+ align-items: center;
80
+ justify-content: space-between;
81
+ }
82
+
83
+ .history-panel__header h2 {
84
+ margin: 0;
85
+ font-size: var(--tr-font-size-md);
86
+ }
87
+
88
+ .new-chat-btn {
89
+ border: none;
90
+ border-radius: 6px;
91
+ background: var(--tr-color-primary);
92
+ color: #fff;
93
+ font-size: 12px;
94
+ padding: 6px 10px;
95
+ cursor: pointer;
96
+ }
97
+
98
+ .history-panel__list {
99
+ flex: 1;
100
+ min-height: 0;
101
+ overflow: auto;
102
+ padding: 12px;
103
+ --tr-history-item-selected-bg: var(--tr-history-item-hover-bg);
104
+ --tr-history-item-space-y: 4px;
105
+ }
106
+
107
+ .history-drawer-backdrop {
108
+ display: none;
109
+ }
110
+
111
+ @media (max-width: 959px) {
112
+ .history-drawer-backdrop {
113
+ display: block;
114
+ position: fixed;
115
+ inset: 0;
116
+ z-index: var(--tr-z-index-drawer);
117
+ background: rgba(0, 0, 0, 0.4);
118
+ }
119
+
120
+ .history-panel {
121
+ position: fixed;
122
+ left: 0;
123
+ top: 0;
124
+ bottom: 0;
125
+ width: min(300px, 80vw);
126
+ z-index: calc(var(--tr-z-index-drawer) + 1);
127
+ background: var(--tr-container-bg-default);
128
+ transform: translateX(-100%);
129
+ transition: transform 0.2s ease;
130
+ }
131
+
132
+ .history-panel--open {
133
+ transform: translateX(0);
134
+ }
135
+ }
136
+ </style>
@@ -0,0 +1,43 @@
1
+ <template>
2
+ <button class="history-trigger-btn" type="button" aria-label="打开历史会话抽屉" @click="emit('click')">
3
+ <IconHistory style="font-size: 20px" />
4
+ </button>
5
+ </template>
6
+
7
+ <script setup lang="ts">
8
+ import { IconHistory } from '@opentiny/tiny-robot-svgs'
9
+
10
+ const emit = defineEmits<{
11
+ (event: 'click'): void
12
+ }>()
13
+ </script>
14
+
15
+ <style scoped>
16
+ .history-trigger-btn {
17
+ display: none;
18
+ }
19
+
20
+ @media (max-width: 959px) {
21
+ .history-trigger-btn {
22
+ display: inline-flex;
23
+ align-items: center;
24
+ justify-content: center;
25
+ position: absolute;
26
+ left: 0;
27
+ top: 50%;
28
+ transform: translateY(-50%);
29
+ width: 32px;
30
+ height: 32px;
31
+ border: none;
32
+ border-radius: var(--tr-radius-full);
33
+ background: var(--tr-container-bg-default);
34
+ color: var(--tr-text-secondary);
35
+ cursor: pointer;
36
+ }
37
+
38
+ .history-trigger-btn:hover {
39
+ color: var(--tr-icon-color-hover);
40
+ background: var(--tr-container-bg-hover);
41
+ }
42
+ }
43
+ </style>
@@ -0,0 +1,278 @@
1
+ <template>
2
+ <div>
3
+ <button
4
+ class="sender-action-btn sender-capability-btn"
5
+ :class="{ 'sender-capability-btn--active': inUseMcpServers.length > 0 }"
6
+ type="button"
7
+ @click="mcpPickerVisible = true"
8
+ >
9
+ <IconPlugin :size="16" class="sender-action-btn__icon" />
10
+ MCP
11
+ <span v-if="inUseMcpServers.length > 0" class="mcp-active-count">
12
+ {{ inUseMcpServers.length }}
13
+ </span>
14
+ </button>
15
+
16
+ <McpServerPicker
17
+ v-model:visible="mcpPickerVisible"
18
+ :active-count="inUseMcpServers.length"
19
+ :popup-config="pickerPopupConfig"
20
+ :installed-plugins="installedPlugins"
21
+ :market-plugins="marketPlugins"
22
+ :loading="false"
23
+ :market-loading="false"
24
+ :show-custom-add-button="false"
25
+ :allow-plugin-delete="true"
26
+ @plugin-toggle="handlePluginToggle"
27
+ @plugin-add="handlePluginAdd"
28
+ @plugin-delete="handlePluginDelete"
29
+ @tool-toggle="handleToolToggle"
30
+ />
31
+ </div>
32
+ </template>
33
+
34
+ <script setup lang="ts">
35
+ import { McpServerPicker, type PluginInfo } from '@opentiny/tiny-robot'
36
+ import { computed, ref } from 'vue'
37
+ import { useMcp } from '../composables/useMcp'
38
+ import type { McpServerKey } from '../mcpServers'
39
+ import { IconPlugin } from './icons'
40
+
41
+ const {
42
+ McpServers,
43
+ availableMcpServers,
44
+ addedMcpServers,
45
+ addMcpServer,
46
+ inUseMcpServers,
47
+ toggleMcpServer,
48
+ removeMcpServer,
49
+ listTools,
50
+ } = useMcp()
51
+
52
+ const mcpPickerVisible = ref(false)
53
+ const loadedPluginTools = ref<Record<string, PluginInfo['tools']>>({})
54
+ const addingMcpServers = ref<Set<McpServerKey>>(new Set())
55
+ const pluginIcon = 'https://modelcontextprotocol.io/favicon.ico'
56
+ const pickerPopupConfig = {
57
+ type: 'drawer' as const,
58
+ drawer: {
59
+ direction: 'right' as const,
60
+ },
61
+ }
62
+
63
+ const inUseServerSet = computed(() => new Set(inUseMcpServers.value))
64
+ const addedServerSet = computed(() => new Set(addedMcpServers.value))
65
+
66
+ function toMcpServerKey(serverKey: string): McpServerKey | null {
67
+ return availableMcpServers.value.includes(serverKey as McpServerKey) ? (serverKey as McpServerKey) : null
68
+ }
69
+
70
+ function getServerDescription(serverKey: McpServerKey): string {
71
+ const config = McpServers[serverKey]
72
+ const description = 'description' in config ? config.description : undefined
73
+ return description || config.baseUrl
74
+ }
75
+
76
+ function getServerIcon(serverKey: McpServerKey): string {
77
+ const config = McpServers[serverKey]
78
+ const logoUrl = 'logoUrl' in config ? config.logoUrl : undefined
79
+ return logoUrl || pluginIcon
80
+ }
81
+
82
+ function normalizeTool(tool: unknown, fallbackIndex: number, enabled: boolean) {
83
+ if (typeof tool !== 'object' || !tool) {
84
+ return {
85
+ id: `tool-${fallbackIndex}`,
86
+ name: `Tool ${fallbackIndex + 1}`,
87
+ description: '',
88
+ enabled,
89
+ }
90
+ }
91
+ const rawName = (tool as { name?: unknown }).name
92
+ const rawDescription = (tool as { description?: unknown }).description
93
+ const name = typeof rawName === 'string' && rawName.length > 0 ? rawName : `Tool ${fallbackIndex + 1}`
94
+ return {
95
+ id: name,
96
+ name,
97
+ description: typeof rawDescription === 'string' ? rawDescription : '',
98
+ enabled,
99
+ }
100
+ }
101
+
102
+ function setLoadedTools(serverKey: McpServerKey, tools: PluginInfo['tools']) {
103
+ loadedPluginTools.value = {
104
+ ...loadedPluginTools.value,
105
+ [serverKey]: tools,
106
+ }
107
+ }
108
+
109
+ function setAddingState(serverKey: McpServerKey, adding: boolean) {
110
+ const next = new Set(addingMcpServers.value)
111
+ if (adding) {
112
+ next.add(serverKey)
113
+ } else {
114
+ next.delete(serverKey)
115
+ }
116
+ addingMcpServers.value = next
117
+ }
118
+
119
+ function mapToolsToPluginTools(tools: unknown[], enabled: boolean): PluginInfo['tools'] {
120
+ return tools.map((tool, index) => normalizeTool(tool, index, enabled))
121
+ }
122
+
123
+ async function loadToolsForServer(serverKey: McpServerKey) {
124
+ try {
125
+ const tools = await listTools(serverKey)
126
+ setLoadedTools(serverKey, mapToolsToPluginTools(tools, true))
127
+ } catch (error) {
128
+ console.error(`[MCP] Failed to load tools for "${serverKey}":`, error)
129
+ setLoadedTools(serverKey, [])
130
+ }
131
+ }
132
+
133
+ const installedPlugins = computed<PluginInfo[]>(() =>
134
+ addedMcpServers.value.map((serverKey) => {
135
+ const config = McpServers[serverKey]
136
+ const enabled = inUseServerSet.value.has(serverKey)
137
+ const tools = (loadedPluginTools.value[serverKey] || []).map((tool) => ({
138
+ ...tool,
139
+ enabled,
140
+ }))
141
+ return {
142
+ id: serverKey,
143
+ name: config.name,
144
+ icon: getServerIcon(serverKey),
145
+ description: getServerDescription(serverKey),
146
+ enabled,
147
+ expanded: true,
148
+ tools,
149
+ }
150
+ }),
151
+ )
152
+
153
+ const marketPlugins = computed<PluginInfo[]>(() =>
154
+ availableMcpServers.value.map((serverKey) => {
155
+ const config = McpServers[serverKey]
156
+ return {
157
+ id: serverKey,
158
+ name: config.name,
159
+ icon: getServerIcon(serverKey),
160
+ description: getServerDescription(serverKey),
161
+ enabled: false,
162
+ tools: [],
163
+ addState: addedServerSet.value.has(serverKey)
164
+ ? 'added'
165
+ : addingMcpServers.value.has(serverKey)
166
+ ? 'loading'
167
+ : 'idle',
168
+ }
169
+ }),
170
+ )
171
+
172
+ async function handlePluginAdd(plugin: PluginInfo) {
173
+ const serverKey = toMcpServerKey(plugin.id)
174
+ if (!serverKey) {
175
+ return
176
+ }
177
+ if (addedServerSet.value.has(serverKey) || addingMcpServers.value.has(serverKey)) {
178
+ return
179
+ }
180
+ setAddingState(serverKey, true)
181
+ try {
182
+ addMcpServer(serverKey)
183
+ await loadToolsForServer(serverKey)
184
+ } finally {
185
+ setAddingState(serverKey, false)
186
+ }
187
+ }
188
+
189
+ async function handlePluginToggle(plugin: PluginInfo, enabled: boolean) {
190
+ const serverKey = toMcpServerKey(plugin.id)
191
+ if (!serverKey) {
192
+ return
193
+ }
194
+ const isEnabled = inUseServerSet.value.has(serverKey)
195
+ if (enabled !== isEnabled) {
196
+ toggleMcpServer(serverKey)
197
+ }
198
+ if (enabled && !loadedPluginTools.value[serverKey]) {
199
+ await loadToolsForServer(serverKey)
200
+ }
201
+ }
202
+
203
+ function handleToolToggle(plugin: PluginInfo, toolId: string, enabled: boolean) {
204
+ const serverKey = toMcpServerKey(plugin.id)
205
+ if (!serverKey) {
206
+ return
207
+ }
208
+ const tools = loadedPluginTools.value[serverKey] || []
209
+ setLoadedTools(
210
+ serverKey,
211
+ tools.map((tool) => (tool.id === toolId ? { ...tool, enabled } : tool)),
212
+ )
213
+ }
214
+
215
+ function handlePluginDelete(plugin: PluginInfo) {
216
+ const serverKey = toMcpServerKey(plugin.id)
217
+ if (!serverKey) {
218
+ return
219
+ }
220
+ removeMcpServer(serverKey)
221
+ const nextLoadedTools = { ...loadedPluginTools.value }
222
+ delete nextLoadedTools[serverKey]
223
+ loadedPluginTools.value = nextLoadedTools
224
+ }
225
+ </script>
226
+
227
+ <style scoped>
228
+ .sender-action-btn {
229
+ border: 1px solid var(--tr-border-color-disabled);
230
+ border-radius: var(--tr-radius-full);
231
+ background: var(--tr-container-bg-default);
232
+ color: var(--tr-text-secondary);
233
+ font-size: var(--tr-font-size-sm);
234
+ height: 32px;
235
+ padding: 0 10px;
236
+ line-height: 1;
237
+ cursor: pointer;
238
+ display: inline-flex;
239
+ align-items: center;
240
+ gap: 6px;
241
+ }
242
+
243
+ .sender-action-btn:hover:not(:disabled) {
244
+ border-color: var(--tr-border-color-hover);
245
+ color: var(--tr-text-primary);
246
+ background: var(--tr-container-bg-hover);
247
+ }
248
+
249
+ .sender-action-btn__icon {
250
+ flex-shrink: 0;
251
+ }
252
+
253
+ .sender-capability-btn--active {
254
+ border-color: var(--tr-border-color-hover);
255
+ color: var(--tr-text-primary);
256
+ background: var(--tr-container-bg-default-2);
257
+ }
258
+
259
+ .mcp-active-count {
260
+ min-width: 14px;
261
+ height: 14px;
262
+ border-radius: 999px;
263
+ background: var(--tr-color-brand, #1476ff);
264
+ color: #fff;
265
+ display: inline-flex;
266
+ align-items: center;
267
+ justify-content: center;
268
+ font-size: 10px;
269
+ padding: 0 3px;
270
+ }
271
+
272
+ :deep(.mcp-server-picker.popup-type-drawer) {
273
+ right: 0 !important;
274
+ top: 0 !important;
275
+ bottom: 0 !important;
276
+ border-right: 0;
277
+ }
278
+ </style>
@@ -0,0 +1,44 @@
1
+ <template>
2
+ <button class="theme-toggle-btn" type="button" aria-label="切换主题" @click="toggleColorMode()">
3
+ <IconMoon v-if="resolvedColorMode === 'light'" :size="20" />
4
+ <IconSun v-else :size="20" />
5
+ </button>
6
+ </template>
7
+
8
+ <script setup lang="ts">
9
+ import { useTheme } from '@opentiny/tiny-robot'
10
+ import { IconMoon, IconSun } from './icons'
11
+
12
+ const { toggleColorMode, resolvedColorMode } = useTheme()
13
+ </script>
14
+
15
+ <style scoped>
16
+ .theme-toggle-btn {
17
+ display: inline-flex;
18
+ align-items: center;
19
+ justify-content: center;
20
+ margin-left: auto;
21
+ width: 32px;
22
+ height: 32px;
23
+ padding: 0;
24
+ border: none;
25
+ border-radius: var(--tr-radius-full);
26
+ background: transparent;
27
+ color: var(--tr-icon-color-default);
28
+ cursor: pointer;
29
+ }
30
+
31
+ .theme-toggle-btn:hover {
32
+ color: var(--tr-icon-color-hover);
33
+ background: var(--tr-container-bg-hover);
34
+ }
35
+
36
+ @media (max-width: 959px) {
37
+ .theme-toggle-btn {
38
+ position: absolute;
39
+ right: 0;
40
+ top: 50%;
41
+ transform: translateY(-50%);
42
+ }
43
+ }
44
+ </style>
@@ -0,0 +1,29 @@
1
+ <script setup lang="ts">
2
+ withDefaults(
3
+ defineProps<{
4
+ size?: number
5
+ }>(),
6
+ {
7
+ size: 24,
8
+ },
9
+ )
10
+ </script>
11
+
12
+ <template>
13
+ <svg
14
+ xmlns="http://www.w3.org/2000/svg"
15
+ xmlns:xlink="http://www.w3.org/1999/xlink"
16
+ :width="size"
17
+ :height="size"
18
+ viewBox="0 0 16 16"
19
+ fill="none"
20
+ >
21
+ <rect id="深度思考-deepthinking 1" width="16.000000" height="16.000000" x="0.000000" y="0.000000" />
22
+ <path
23
+ id="矢量_163"
24
+ d="M13.6607 13.6627C13.7407 13.5827 13.8107 13.4927 13.8807 13.4027C14.1707 13.0127 14.3207 12.5327 14.3507 11.9527C14.3807 11.3227 14.2507 10.6127 13.9707 9.83266C13.7507 9.23266 13.4507 8.62267 13.0807 8.00266C13.4607 7.38266 13.7607 6.77266 13.9707 6.17266C14.2507 5.39266 14.3807 4.68266 14.3507 4.05266C14.3207 3.34266 14.0807 2.77266 13.6607 2.34266C13.2307 1.91266 12.6607 1.68266 11.9507 1.65266C11.3207 1.62266 10.6107 1.75266 9.83071 2.03266C9.23071 2.25266 8.62071 2.55266 8.00071 2.93266C7.38071 2.55266 6.77071 2.25266 6.17071 2.03266C5.39071 1.75266 4.69071 1.62266 4.06071 1.65266C3.48071 1.68266 2.99071 1.84266 2.60071 2.12266C2.51071 2.19266 2.43071 2.27266 2.35071 2.35266C2.27071 2.43266 2.19071 2.51266 2.12071 2.60266C1.84071 2.99266 1.68071 3.48266 1.65071 4.06266C1.62071 4.69266 1.75071 5.40266 2.03071 6.18266C2.25071 6.77266 2.55071 7.38266 2.93071 8.00266C2.55071 8.62267 2.25071 9.23266 2.03071 9.83266C1.75071 10.6127 1.62071 11.3227 1.65071 11.9527C1.68071 12.5327 1.83071 13.0127 2.12071 13.4027C2.19071 13.4927 2.26071 13.5827 2.34071 13.6627C2.42071 13.7427 2.51071 13.8127 2.60071 13.8827C2.99071 14.1727 3.47071 14.3227 4.05071 14.3527C4.68071 14.3827 5.39071 14.2527 6.17071 13.9727C6.77071 13.7527 7.38071 13.4527 8.00071 13.0827C8.62071 13.4627 9.23071 13.7627 9.83071 13.9727C10.6107 14.2527 11.3207 14.3827 11.9507 14.3527C12.5307 14.3227 13.0107 14.1627 13.4007 13.8827C13.4907 13.8127 13.5807 13.7427 13.6607 13.6627ZM7.08071 3.55266C6.65071 3.31266 6.24071 3.12266 5.83071 2.97266C5.18071 2.74266 4.60071 2.63266 4.10071 2.65266C3.66071 2.68266 3.32071 2.80266 3.07071 3.03266C2.83071 3.28266 2.68071 3.65266 2.65071 4.10266C2.63071 4.60266 2.74071 5.18266 2.97071 5.83266C3.12071 6.24266 3.31071 6.65266 3.54071 7.07266C4.02071 6.41266 4.56071 5.78266 5.17071 5.17266C5.78071 4.57266 6.42071 4.02266 7.08071 3.55266ZM12.4607 7.08266C12.6907 6.66266 12.8807 6.24266 13.0307 5.84266C13.2707 5.19266 13.3807 4.61266 13.3507 4.11266C13.3307 3.65266 13.1907 3.30266 12.9507 3.06266C12.7107 2.82266 12.3607 2.68266 11.9007 2.66266C11.4007 2.64266 10.8307 2.74266 10.1707 2.98266C9.77071 3.12266 9.35071 3.31266 8.93071 3.55266C9.59071 4.03266 10.2207 4.57266 10.8307 5.18266C11.4407 5.78266 11.9807 6.42266 12.4607 7.08266ZM11.8907 8.00266C11.3907 8.74266 10.8007 9.44267 10.1207 10.1227C9.45071 10.8027 8.74071 11.3927 8.00071 11.8927C7.26071 11.3927 6.56071 10.8027 5.88071 10.1227C5.20071 9.45267 4.62071 8.74266 4.12071 8.00266C4.62071 7.26266 5.21071 6.56266 5.89071 5.88266C6.56071 5.21266 7.27071 4.62266 8.00071 4.12266C8.74071 4.62266 9.44071 5.21266 10.1207 5.89266C10.8007 6.56266 11.3907 7.27266 11.8907 8.00266ZM8.00071 9.00266C8.55071 9.00266 9.00071 8.55266 9.00071 8.00266C9.00071 7.45266 8.55071 7.00266 8.00071 7.00266C7.45071 7.00266 7.00071 7.45266 7.00071 8.00266C7.00071 8.55266 7.45071 9.00266 8.00071 9.00266ZM3.55071 8.93266C4.03071 9.59266 4.57071 10.2227 5.18071 10.8327C5.79071 11.4427 6.42071 11.9827 7.08071 12.4627C6.66071 12.6927 6.24071 12.8827 5.84071 13.0327C5.19071 13.2727 4.61071 13.3827 4.11071 13.3527C3.67071 13.3327 3.32071 13.2027 3.08071 12.9727C3.07071 12.9627 3.07071 12.9627 3.06071 12.9527C2.83071 12.7127 2.68071 12.3427 2.66071 11.9027C2.64071 11.4027 2.74071 10.8327 2.98071 10.1727C3.12071 9.77267 3.31071 9.35266 3.55071 8.93266ZM8.93071 12.4627C9.35071 12.6927 9.77071 12.8827 10.1707 13.0327C10.8207 13.2727 11.4007 13.3827 11.9007 13.3527C12.3607 13.3327 12.7107 13.1927 12.9507 12.9527C13.1807 12.7127 13.3307 12.3427 13.3507 11.9027C13.3707 11.4027 13.2707 10.8327 13.0307 10.1727C12.8807 9.76266 12.6907 9.35266 12.4607 8.93266C11.9807 9.59266 11.4407 10.2227 10.8307 10.8327C10.2207 11.4427 9.59071 11.9827 8.93071 12.4627Z"
25
+ fill="currentColor"
26
+ fill-rule="evenodd"
27
+ />
28
+ </svg>
29
+ </template>