@opentiny/tiny-robot-cli 0.5.2-alpha.3 → 1.0.0-beta.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 (53) hide show
  1. package/README.md +49 -1
  2. package/bin/commands/add.js +563 -269
  3. package/bin/commands/create.js +6 -3
  4. package/bin/runtime-version.js +37 -0
  5. package/bin/utils.js +147 -45
  6. package/package.json +5 -3
  7. package/templates/basic/.env.example +8 -0
  8. package/templates/basic/README.md +53 -22
  9. package/templates/basic/index.html +2 -2
  10. package/templates/basic/package.json +12 -16
  11. package/templates/basic/public/favicon.svg +1 -0
  12. package/templates/basic/public/icons.svg +24 -0
  13. package/templates/basic/src/App.vue +111 -119
  14. package/templates/basic/src/main.ts +2 -2
  15. package/templates/basic/tsconfig.app.json +2 -5
  16. package/templates/basic/tsconfig.node.json +3 -6
  17. package/templates/basic/vite.config.ts +6 -1
  18. package/templates/chat/.env.example +9 -0
  19. package/templates/chat/README.md +46 -0
  20. package/templates/chat/index.html +12 -0
  21. package/templates/chat/package.json +28 -0
  22. package/templates/chat/src/TinyRobotChat.vue +88 -27
  23. package/templates/chat/src/components/ComposerTools.vue +99 -0
  24. package/templates/chat/src/components/WindowHeader.vue +192 -0
  25. package/templates/chat/src/composables/useWindow.ts +83 -0
  26. package/templates/chat/src/config/chat-runtime.ts +82 -0
  27. package/templates/chat/src/config/chat-ui.ts +289 -0
  28. package/templates/chat/src/index.css +109 -0
  29. package/templates/chat/src/main.ts +7 -0
  30. package/templates/chat/tsconfig.app.json +13 -0
  31. package/templates/chat/tsconfig.json +7 -0
  32. package/templates/chat/tsconfig.node.json +19 -0
  33. package/templates/chat/vite.config.ts +16 -0
  34. package/templates/basic/public/modelcontextprotocol.png +0 -0
  35. package/templates/basic/src/components/ChatList.vue +0 -82
  36. package/templates/basic/src/components/ChatSender.vue +0 -125
  37. package/templates/basic/src/components/ConversationHistory.vue +0 -136
  38. package/templates/basic/src/components/HistoryDrawerButton.vue +0 -43
  39. package/templates/basic/src/components/McpServerPickerButton.vue +0 -278
  40. package/templates/basic/src/components/ThemeToggleButton.vue +0 -44
  41. package/templates/basic/src/components/icons/IconDeepThink.vue +0 -29
  42. package/templates/basic/src/components/icons/IconModelAliyunBailian.vue +0 -51
  43. package/templates/basic/src/components/icons/IconModelDeepseek.vue +0 -29
  44. package/templates/basic/src/components/icons/IconMoon.vue +0 -29
  45. package/templates/basic/src/components/icons/IconPlugin.vue +0 -29
  46. package/templates/basic/src/components/icons/IconSun.vue +0 -35
  47. package/templates/basic/src/components/icons/IconWebSearch.vue +0 -36
  48. package/templates/basic/src/components/icons/index.ts +0 -7
  49. package/templates/basic/src/composables/useChat.ts +0 -129
  50. package/templates/basic/src/composables/useMcp.ts +0 -170
  51. package/templates/basic/src/composables/useModel.ts +0 -82
  52. package/templates/basic/src/mcpServers.ts +0 -40
  53. package/templates/basic/src/models.ts +0 -81
@@ -0,0 +1,109 @@
1
+ .chat-add-app,
2
+ .chat-add-window {
3
+ font-family:
4
+ Inter,
5
+ -apple-system,
6
+ BlinkMacSystemFont,
7
+ 'Segoe UI',
8
+ sans-serif;
9
+ color: #1f2329;
10
+ }
11
+ .chat-add-app,
12
+ .chat-add-app *,
13
+ .chat-add-window,
14
+ .chat-add-window * {
15
+ box-sizing: border-box;
16
+ }
17
+ .chat-add-app {
18
+ position: relative;
19
+ container-type: inline-size;
20
+ display: flex;
21
+ width: 100%;
22
+ height: 100%;
23
+ min-width: 0;
24
+ min-height: 0;
25
+ }
26
+ .chat-add-launcher {
27
+ position: fixed;
28
+ right: 24px;
29
+ bottom: 24px;
30
+ z-index: var(--tr-z-index-popover, 1000);
31
+ display: inline-flex;
32
+ align-items: center;
33
+ justify-content: center;
34
+ width: 56px;
35
+ height: 56px;
36
+ padding: 0;
37
+ border: 0;
38
+ border-radius: 50%;
39
+ background: var(--tr-container-bg-default);
40
+ color: var(--tr-color-primary);
41
+ box-shadow: 0 8px 24px rgb(0 0 0 / 20%);
42
+ cursor: pointer;
43
+ }
44
+ .chat-add-launcher:hover {
45
+ background: var(--tr-color-primary-light);
46
+ }
47
+ .chat-add-launcher:focus-visible {
48
+ outline: 2px solid var(--tr-color-primary);
49
+ outline-offset: 3px;
50
+ }
51
+ .chat-add-window {
52
+ container-type: inline-size;
53
+ --tr-layout-bg-default: var(--tr-container-bg-default);
54
+ --tr-chat-ui-header-bg: var(--tr-container-bg-default);
55
+ --tr-chat-ui-main-bg: var(--tr-page-bg-default);
56
+ --tr-chat-ui-footer-bg: var(--tr-page-bg-default);
57
+ --tr-suggestion-popover-width: min(440px, calc(100cqw - 16px));
58
+ width: 100%;
59
+ min-width: 0;
60
+ min-height: 0;
61
+ }
62
+ .chat-add-window .tr-welcome {
63
+ box-sizing: border-box;
64
+ width: 100%;
65
+ padding: 24px 24px 0;
66
+ }
67
+ .chat-add-window .tr-welcome__title-wrapper {
68
+ display: flex;
69
+ align-items: center;
70
+ justify-content: center;
71
+ }
72
+ .chat-add-window .tr-prompts {
73
+ --tr-prompts-gap: 16px;
74
+ box-sizing: border-box;
75
+ width: 100%;
76
+ padding: 16px 24px;
77
+ }
78
+ .chat-add-window .prompt-item {
79
+ flex: 0 0 calc((100% - 16px) / 2);
80
+ box-sizing: border-box;
81
+ min-width: 0;
82
+ }
83
+ .chat-add-window--floating {
84
+ border-radius: var(--tr-layout-floating-radius);
85
+ box-shadow: var(--tr-layout-floating-shadow);
86
+ overflow: hidden;
87
+ }
88
+ .chat-add-window--fullscreen {
89
+ border-radius: 0;
90
+ box-shadow: none;
91
+ }
92
+ @container (max-width: 519px) {
93
+ .chat-add-launcher {
94
+ right: 16px;
95
+ bottom: 16px;
96
+ }
97
+ .chat-add-window .prompt-item {
98
+ flex-basis: 100%;
99
+ }
100
+ .chat-add-window .tr-welcome {
101
+ padding-inline: 12px;
102
+ }
103
+ .chat-add-window .tr-prompts {
104
+ padding-inline: 12px;
105
+ }
106
+ .chat-add-window .tr-bubble__avatar {
107
+ display: none;
108
+ }
109
+ }
@@ -0,0 +1,7 @@
1
+ import { createApp } from 'vue'
2
+ import '@opentiny/tiny-robot/dist/style.css'
3
+ import '@opentiny/tiny-robot-chat/dist/style.css'
4
+ import './index.css'
5
+ import TinyRobotChat from './TinyRobotChat.vue'
6
+
7
+ createApp(TinyRobotChat).mount('#app')
@@ -0,0 +1,13 @@
1
+ {
2
+ "extends": "@vue/tsconfig/tsconfig.dom.json",
3
+ "compilerOptions": {
4
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
5
+ "types": ["vite/client"],
6
+ "allowArbitraryExtensions": true,
7
+ "noUnusedLocals": true,
8
+ "noUnusedParameters": true,
9
+ "erasableSyntaxOnly": true,
10
+ "noFallthroughCasesInSwitch": true
11
+ },
12
+ "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
13
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "files": [],
3
+ "references": [
4
+ { "path": "./tsconfig.app.json" },
5
+ { "path": "./tsconfig.node.json" }
6
+ ]
7
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
4
+ "target": "es2023",
5
+ "lib": ["ES2023"],
6
+ "types": ["node"],
7
+ "skipLibCheck": true,
8
+ "module": "nodenext",
9
+ "allowImportingTsExtensions": true,
10
+ "verbatimModuleSyntax": true,
11
+ "moduleDetection": "force",
12
+ "noEmit": true,
13
+ "noUnusedLocals": true,
14
+ "noUnusedParameters": true,
15
+ "erasableSyntaxOnly": true,
16
+ "noFallthroughCasesInSwitch": true
17
+ },
18
+ "include": ["vite.config.ts"]
19
+ }
@@ -0,0 +1,16 @@
1
+ import { defineConfig } from 'vite'
2
+ import vue from '@vitejs/plugin-vue'
3
+
4
+ // https://vite.dev/config/
5
+ export default defineConfig({
6
+ plugins: [vue()],
7
+ server: {
8
+ proxy: {
9
+ '/modelcontextprotocol-mcp': {
10
+ target: 'https://modelcontextprotocol.io/mcp',
11
+ changeOrigin: true,
12
+ rewrite: (path) => path.replace(/^\/modelcontextprotocol-mcp/, ''),
13
+ },
14
+ },
15
+ },
16
+ })
@@ -1,82 +0,0 @@
1
- <template>
2
- <tr-bubble-provider :fallback-content-renderer="BubbleRenderers.Markdown">
3
- <tr-welcome
4
- v-if="visibleMessages.length === 0"
5
- title="TinyRobot AI 助手"
6
- description="您好,我是TinyRobot,您专属的 AI 智能专家"
7
- :icon="welcomeIcon"
8
- class="chat-list chat-welcome"
9
- />
10
- <tr-bubble-list
11
- v-else
12
- :messages="messages"
13
- :role-configs="roles"
14
- :auto-scroll="true"
15
- class="chat-list"
16
- ></tr-bubble-list>
17
- </tr-bubble-provider>
18
- </template>
19
-
20
- <script setup lang="ts">
21
- import { BubbleRenderers, TrBubbleList, TrBubbleProvider, TrWelcome, type BubbleRoleConfig } from '@opentiny/tiny-robot'
22
- import { IconAi, IconUser } from '@opentiny/tiny-robot-svgs'
23
- import { computed, h } from 'vue'
24
- import { useChat } from '../composables/useChat'
25
-
26
- const { messages } = useChat()
27
-
28
- const aiAvatar = h(IconAi, { style: { fontSize: '28px' } })
29
- const userAvatar = h(IconUser, { style: { fontSize: '28px' } })
30
- const welcomeIcon = h(IconAi, { style: { fontSize: '40px' } })
31
- const visibleMessages = computed(() => messages.value.filter((item) => item.role !== 'system'))
32
-
33
- const roles: Record<string, BubbleRoleConfig> = {
34
- assistant: {
35
- placement: 'start',
36
- avatar: aiAvatar,
37
- },
38
- user: {
39
- placement: 'end',
40
- avatar: userAvatar,
41
- },
42
- system: {
43
- hidden: true,
44
- },
45
- }
46
- </script>
47
-
48
- <style scoped>
49
- .chat-list {
50
- flex: 1;
51
- min-height: 0;
52
- overflow: auto;
53
- border-radius: 10px;
54
- padding: 8px;
55
- }
56
-
57
- .chat-welcome {
58
- display: flex;
59
- align-items: center;
60
- justify-content: center;
61
- margin-bottom: 10%;
62
-
63
- &.tr-welcome {
64
- --title-color: var(--tr-text-primary);
65
- --description-color: var(--tr-text-secondary);
66
- }
67
- }
68
-
69
- :deep() {
70
- [data-box-type='box'][data-role='user'] {
71
- --tr-bubble-box-bg: var(--tr-color-primary-light);
72
- }
73
-
74
- [data-box-type='box']:not([data-role='user']) {
75
- --tr-bubble-box-bg: transparent;
76
- }
77
-
78
- [data-type='markdown'] p {
79
- margin: 0;
80
- }
81
- }
82
- </style>
@@ -1,125 +0,0 @@
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>
@@ -1,136 +0,0 @@
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>
@@ -1,43 +0,0 @@
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>