@opentiny/vue-docs 3.23.1 → 3.24.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 (71) hide show
  1. package/demos/apis/dialog-select.js +42 -0
  2. package/demos/apis/popeditor.js +14 -0
  3. package/demos/apis/steps.js +15 -0
  4. package/demos/apis/text-popup.js +2 -2
  5. package/demos/apis/user-head.js +7 -18
  6. package/demos/mobile-first/app/pager/webdoc/pager.js +0 -48
  7. package/demos/mobile-first/app/steps/vertical.vue +14 -1
  8. package/demos/pc/app/calendar-view/calendar-event.spec.ts +2 -2
  9. package/demos/pc/app/date-panel/basic-usage.spec.ts +6 -6
  10. package/demos/pc/app/date-panel/custom-week.spec.ts +1 -1
  11. package/demos/pc/app/date-panel/disabled-date.spec.ts +8 -9
  12. package/demos/pc/app/date-panel/event.spec.ts +4 -4
  13. package/demos/pc/app/date-panel/format.spec.ts +2 -2
  14. package/demos/pc/app/date-panel/readonly.spec.ts +3 -3
  15. package/demos/pc/app/date-panel/unlink-panels.spec.ts +4 -4
  16. package/demos/pc/app/date-picker/basic-usage.spec.ts +2 -2
  17. package/demos/pc/app/date-picker/date-range.spec.ts +3 -3
  18. package/demos/pc/app/dialog-select/nest-grid-multi-composition-api.vue +19 -1
  19. package/demos/pc/app/dialog-select/nest-grid-multi.spec.ts +19 -0
  20. package/demos/pc/app/dialog-select/nest-grid-multi.vue +17 -1
  21. package/demos/pc/app/dialog-select/webdoc/dialog-select.js +2 -2
  22. package/demos/pc/app/grid/base/basic-usage-composition-api.vue +48 -93
  23. package/demos/pc/app/grid/base/basic-usage.spec.js +1 -1
  24. package/demos/pc/app/grid/base/basic-usage.vue +29 -132
  25. package/demos/pc/app/grid/dynamically-columns/dynamically-columns.spec.js +3 -3
  26. package/demos/pc/app/grid/webdoc/grid-ai-agent.js +23 -0
  27. package/demos/pc/app/popeditor/condition-layout-composition-api.vue +1 -0
  28. package/demos/pc/app/popeditor/condition-layout.spec.ts +1 -0
  29. package/demos/pc/app/popeditor/condition-layout.vue +1 -0
  30. package/demos/pc/app/popeditor/webdoc/popeditor.js +2 -2
  31. package/demos/pc/app/qr-code/style-composition-api.vue +14 -3
  32. package/demos/pc/app/qr-code/style.vue +15 -3
  33. package/demos/pc/app/text-popup/{value.spec.ts → modelValue.spec.ts} +2 -2
  34. package/demos/pc/app/text-popup/webdoc/text-popup.js +8 -8
  35. package/demos/pc/webdoc/changelog.md +452 -452
  36. package/package.json +27 -19
  37. package/playground/App.vue +2 -2
  38. package/src/App.vue +18 -1
  39. package/src/{views/components-doc/components → components}/demo.vue +1 -1
  40. package/src/{views/components-doc/components → components}/float-settings.vue +24 -7
  41. package/src/components/mcp-docs.vue +55 -0
  42. package/src/components/tiny-robot-chat.vue +128 -0
  43. package/src/composable/DifyModelProvider.ts +65 -0
  44. package/src/composable/storage.ts +71 -0
  45. package/src/composable/useTinyRobot.ts +167 -0
  46. package/src/composable/utils.ts +172 -0
  47. package/src/i18n/index.js +5 -2
  48. package/src/main.js +10 -1
  49. package/src/router.js +9 -0
  50. package/src/tools/appData.js +12 -2
  51. package/src/views/components-doc/common.vue +22 -8
  52. package/src/views/comprehensive/Demo.vue +211 -0
  53. package/src/views/comprehensive/index.vue +391 -0
  54. package/src/views/comprehensive/products.json +99 -0
  55. package/src/views/comprehensive/types/index.ts +37 -0
  56. package/src/views/layout/layout.vue +2 -2
  57. package/demos/mobile-first/app/pager/current-change.vue +0 -34
  58. package/demos/mobile-first/app/pager/next-click.vue +0 -34
  59. package/demos/mobile-first/app/pager/prev-click.vue +0 -34
  60. /package/demos/pc/app/text-popup/{clear-value-composition-api.vue → clear-modelValue-composition-api.vue} +0 -0
  61. /package/demos/pc/app/text-popup/{clear-value.spec.ts → clear-modelValue.spec.ts} +0 -0
  62. /package/demos/pc/app/text-popup/{clear-value.vue → clear-modelValue.vue} +0 -0
  63. /package/demos/pc/app/text-popup/{value-composition-api.vue → modelValue-composition-api.vue} +0 -0
  64. /package/demos/pc/app/text-popup/{value.vue → modelValue.vue} +0 -0
  65. /package/src/{views/components-doc/components → components}/anchor.vue +0 -0
  66. /package/src/{views/components-doc/components → components}/api-docs.vue +0 -0
  67. /package/src/{views/components-doc/components → components}/async-highlight.vue +0 -0
  68. /package/src/{views/components-doc/components → components}/contributor.vue +0 -0
  69. /package/src/{views/components-doc/components → components}/header.vue +0 -0
  70. /package/src/{views/components-doc/components → components}/version-tip.vue +0 -0
  71. /package/src/{views/components-doc/composition → composable}/useTasksFinish.ts +0 -0
package/package.json CHANGED
@@ -1,18 +1,26 @@
1
1
  {
2
2
  "name": "@opentiny/vue-docs",
3
3
  "type": "module",
4
- "version": "3.23.1",
4
+ "version": "3.24.0",
5
5
  "license": "MIT",
6
6
  "dependencies": {
7
7
  "@docsearch/css": "^3.8.0",
8
8
  "@docsearch/js": "^3.8.0",
9
9
  "@docsearch/react": "npm:@docsearch/css",
10
+ "@opentiny/next": "0.1.2",
11
+ "@opentiny/next-vue": "0.0.1-alpha.1",
12
+ "@opentiny/tiny-robot": "0.2.1",
13
+ "@opentiny/tiny-robot-kit": "0.2.1",
14
+ "@opentiny/tiny-robot-svgs": "0.2.1",
15
+ "@opentiny/tiny-vue-mcp": "0.0.1-alpha.3",
10
16
  "@opentiny/vue-repl": "^1.1.2",
11
17
  "@opentiny/vue-vite-import": "~1.2.0",
12
18
  "@unocss/reset": "0.38.2",
13
19
  "@vue/repl": "^2.5.5",
20
+ "@vue/shared": "^3.4.31",
14
21
  "@vueuse/core": "^12.7.0",
15
22
  "@vueuse/head": "0.7.13",
23
+ "crypto-js": "^4.2.0",
16
24
  "github-markdown-css": "~5.1.0",
17
25
  "highlight.js": "^11.5.1",
18
26
  "marked": "^4.3.0",
@@ -22,24 +30,24 @@
22
30
  "vue": "^3.4.31",
23
31
  "vue-i18n": "~9.14.3",
24
32
  "vue-router": "4.1.5",
25
- "@opentiny/utils": "~3.23.0",
26
- "@opentiny/vue": "~3.23.0",
27
- "@opentiny/vue-common": "~3.23.0",
28
- "@opentiny/vue-design-aurora": "~3.23.0",
29
- "@opentiny/vue-design-smb": "~3.23.0",
30
- "@opentiny/vue-directive": "~3.23.0",
31
- "@opentiny/vue-hooks": "~3.23.0",
32
- "@opentiny/vue-flowchart": "~3.23.0",
33
- "@opentiny/vue-design-saas": "~3.23.0",
34
- "@opentiny/vue-icon": "~3.23.0",
35
- "@opentiny/vue-icon-multicolor": "~3.23.0",
36
- "@opentiny/vue-icon-saas": "~3.23.0",
37
- "@opentiny/vue-renderless": "~3.23.0",
38
- "@opentiny/vue-huicharts": "~3.23.0",
39
- "@opentiny/vue-theme": "~3.23.0",
40
- "@opentiny/vue-locale": "~3.23.0",
41
- "@opentiny/vue-modal": "~3.23.0",
42
- "@opentiny/vue-theme-saas": "~3.23.0"
33
+ "@opentiny/utils": "~3.24.0",
34
+ "@opentiny/vue-common": "~3.24.0",
35
+ "@opentiny/vue": "~3.24.0",
36
+ "@opentiny/vue-design-saas": "~3.24.0",
37
+ "@opentiny/vue-design-smb": "~3.24.0",
38
+ "@opentiny/vue-directive": "~3.24.0",
39
+ "@opentiny/vue-flowchart": "~3.24.0",
40
+ "@opentiny/vue-icon": "~3.24.0",
41
+ "@opentiny/vue-huicharts": "~3.24.0",
42
+ "@opentiny/vue-hooks": "~3.24.0",
43
+ "@opentiny/vue-design-aurora": "~3.24.0",
44
+ "@opentiny/vue-icon-saas": "~3.24.0",
45
+ "@opentiny/vue-icon-multicolor": "~3.24.0",
46
+ "@opentiny/vue-locale": "~3.24.0",
47
+ "@opentiny/vue-theme-saas": "~3.24.0",
48
+ "@opentiny/vue-renderless": "~3.24.0",
49
+ "@opentiny/vue-modal": "~3.24.0",
50
+ "@opentiny/vue-theme": "~3.24.0"
43
51
  },
44
52
  "devDependencies": {
45
53
  "@playwright/test": "~1.49.0",
@@ -11,7 +11,7 @@ import logoUrl from './assets/opentiny-logo.svg?url'
11
11
  import GitHub from './icons/Github.vue'
12
12
  import Share from './icons/Share.vue'
13
13
 
14
- const VERSION = 'tiny-vue-version-3.23'
14
+ const VERSION = 'tiny-vue-version-3.24'
15
15
  const NOTIFY_KEY = 'tiny-vue-playground-notify'
16
16
  const LAYOUT = 'playground-layout'
17
17
  const LAYOUT_REVERSE = 'playground-layout-reverse'
@@ -23,7 +23,7 @@ const isMobileFirst = tinyMode === 'mobile-first'
23
23
  const isSaas = tinyTheme === 'saas'
24
24
  const isPreview = searchObj.get('openMode') === 'preview' // 是否多端弹窗预览
25
25
 
26
- const versions = ['3.23', '3.22', '3.21']
26
+ const versions = ['3.24', '3.23', '3.22']
27
27
  const getVersion = () => {
28
28
  if (isPreview) {
29
29
  return versions[0]
package/src/App.vue CHANGED
@@ -11,11 +11,13 @@
11
11
  </template>
12
12
 
13
13
  <script>
14
- import { defineComponent, onMounted, provide, ref } from 'vue'
14
+ import { defineComponent, onMounted, provide, ref, watch } from 'vue'
15
15
  import { ConfigProvider, Modal } from '@opentiny/vue'
16
16
  import { iconClose } from '@opentiny/vue-icon'
17
17
  import { appData } from './tools'
18
18
  import useTheme from './tools/useTheme'
19
+ import { useNextClient } from '@opentiny/next-vue'
20
+ import { globalConversation } from './composable/utils'
19
21
 
20
22
  export default defineComponent({
21
23
  name: 'AppVue',
@@ -28,6 +30,21 @@ export default defineComponent({
28
30
  setup() {
29
31
  const previewUrl = ref(import.meta.env.VITE_PLAYGROUND_URL)
30
32
  const modalSHow = ref(false)
33
+
34
+ const { sessionId } = useNextClient({
35
+ clientInfo: { name: 'tiny-vue-website', version: '1.0.0' },
36
+ proxyOptions: { url: 'https://39.108.160.245/sse', token: '' }
37
+ })
38
+
39
+ watch(
40
+ () => sessionId.value,
41
+ (newVal) => {
42
+ if (newVal) {
43
+ globalConversation.sessionId = newVal
44
+ }
45
+ }
46
+ )
47
+
31
48
  onMounted(() => {
32
49
  // 加载header
33
50
  const common = new window.TDCommon(['#header'], {
@@ -75,7 +75,7 @@ import { i18nByKey, getWord } from '@/i18n'
75
75
  import { $split, fetchDemosFile } from '@/tools'
76
76
  import { Tabs as TinyTabs, TabItem as TinyTabItem, Button as TinyButton } from '@opentiny/vue'
77
77
  import { AutoTip as vAutoTip } from '@opentiny/vue-directive'
78
- import { languageMap, vueComponents, getWebdocPath, staticDemoPath } from '../cmp-config'
78
+ import { languageMap, vueComponents, getWebdocPath, staticDemoPath } from '../views/components-doc/cmp-config'
79
79
  import { router } from '@/router.js'
80
80
  import demoConfig from '@demos/config.js'
81
81
  import { useApiMode, useTemplateMode } from '@/tools'
@@ -59,12 +59,21 @@
59
59
  </tiny-radio-group>
60
60
  </div>
61
61
  <template #reference>
62
- <div
63
- class="settings-btn style-settings-btn"
64
- @click="demoStyleVisible = !demoStyleVisible"
65
- @blur="demoStyleVisible = false"
66
- >
67
- <style-settings-icon class="settings-icon style-settings-icon"></style-settings-icon>
62
+ <div>
63
+ <div
64
+ v-if="appData.hasFloatRobot"
65
+ class="settings-btn style-settings-btn"
66
+ @click="appData.showTinyRobot = true"
67
+ >
68
+ <IconAi class="settings-icon style-settings-icon"></IconAi>
69
+ </div>
70
+ <div
71
+ class="settings-btn style-settings-btn"
72
+ @click="demoStyleVisible = !demoStyleVisible"
73
+ @blur="demoStyleVisible = false"
74
+ >
75
+ <style-settings-icon class="settings-icon style-settings-icon"></style-settings-icon>
76
+ </div>
68
77
  </div>
69
78
  </template>
70
79
  </tiny-popover>
@@ -92,8 +101,10 @@ import { iconUpWard } from '@opentiny/vue-icon'
92
101
  import { debounce } from '@opentiny/utils'
93
102
  import { i18nByKey, useApiMode, useTemplateMode } from '@/tools'
94
103
  import useTheme from '@/tools/useTheme'
104
+ import { appData } from '@/tools/appData.js'
95
105
  import { router } from '@/router'
96
106
  import useStyleSettings from '@/tools/useStyleSettings'
107
+ import { IconAi } from '@opentiny/tiny-robot-svgs'
97
108
 
98
109
  // import ThemeSettingsIcon from '@/assets/images/theme-settings.svg'
99
110
  import StyleSettingsIcon from '@/assets/images/style-settings.svg'
@@ -106,6 +117,7 @@ export default defineComponent({
106
117
  TinyRadioGroup: RadioGroup,
107
118
  IconUpWard: iconUpWard(),
108
119
  TinyPopover: Popover,
120
+ IconAi,
109
121
  // ThemeSettingsIcon,
110
122
  StyleSettingsIcon
111
123
  },
@@ -249,13 +261,18 @@ export default defineComponent({
249
261
  i18nByKey,
250
262
  apiModeState,
251
263
  currentThemeKey,
252
- floatSettings
264
+ floatSettings,
265
+ appData
253
266
  }
254
267
  }
255
268
  })
256
269
  </script>
257
270
 
258
271
  <style lang="less">
272
+ .docs-on-robot-show .float-settings {
273
+ right: 680px;
274
+ }
275
+
259
276
  .float-settings {
260
277
  position: fixed;
261
278
  right: 200px;
@@ -0,0 +1,55 @@
1
+ <template>
2
+ <div class="mcp-list">
3
+ <div class="mcp-title">
4
+ <h2>{{ capName }} 组件的 MCP 工具</h2>
5
+ </div>
6
+ <tiny-grid :data="mcpInfo" row-id="name">
7
+ <tiny-grid-column field="name" title="名称" width="180"> </tiny-grid-column>
8
+ <tiny-grid-column field="param" title="参数类型" width="150"> </tiny-grid-column>
9
+ <tiny-grid-column field="desc" title="工具描述"> </tiny-grid-column>
10
+ </tiny-grid>
11
+ </div>
12
+ </template>
13
+
14
+ <script setup>
15
+ import { getTinyVueMcpConfig } from '@opentiny/tiny-vue-mcp'
16
+ import { TinyGrid, TinyGridColumn } from '@opentiny/vue'
17
+ import { camelize, capitalize } from '@vue/shared'
18
+ import { onMounted, computed } from 'vue'
19
+
20
+ const props = defineProps({
21
+ name: String
22
+ })
23
+
24
+ const mcpTools = getTinyVueMcpConfig({ t: null })
25
+ const capName = computed(() => capitalize(camelize(props.name)))
26
+
27
+ const mcpInfo = computed(() => {
28
+ const schema = mcpTools.components[capName.value]?.paramsSchema
29
+ if (schema) {
30
+ return Object.keys(schema).map((name) => {
31
+ const item = schema[name]
32
+ return {
33
+ name,
34
+ param: item._def?.innerType?._def?.typeName || '',
35
+ desc: item._def?.description || ''
36
+ }
37
+ })
38
+ }
39
+ return null
40
+ })
41
+
42
+ onMounted(() => {})
43
+ </script>
44
+
45
+ <style scoped lang="less">
46
+ .mcp-list {
47
+ padding-bottom: 150px;
48
+ }
49
+ .mcp-title {
50
+ font-size: 16px;
51
+ font-weight: bold;
52
+ margin-top: 26px;
53
+ margin-bottom: 26px;
54
+ }
55
+ </style>
@@ -0,0 +1,128 @@
1
+ <template>
2
+ <!-- mcp-robot弹窗 -->
3
+ <tr-container v-model:show="appData.showTinyRobot" v-model:fullscreen="fullscreen">
4
+ <div v-if="messages.length === 0">
5
+ <tr-welcome title="智能助手" description="您好,我是OpenTiny AI智能助手" :icon="welcomeIcon">
6
+ <template #footer>
7
+ <div class="welcome-footer"></div>
8
+ </template>
9
+ </tr-welcome>
10
+ <tr-prompts
11
+ :items="customPromptItems"
12
+ :wrap="true"
13
+ item-class="prompt-item"
14
+ class="tiny-prompts"
15
+ @item-click="handlePromptItemClick"
16
+ ></tr-prompts>
17
+ </div>
18
+ <tr-bubble-list v-else :items="messages" :roles="roles" auto-scroll></tr-bubble-list>
19
+ <template #footer>
20
+ <div class="chat-input">
21
+ <TrSuggestionPills :items="customSuggestionPillItems" @item-click="handleSuggestionPillItemClick" /><br />
22
+ <tr-sender
23
+ ref="senderRef"
24
+ mode="single"
25
+ v-model="inputMessage"
26
+ :placeholder="GeneratingStatus.includes(messageState.status) ? '正在思考中...' : '请输入您的问题'"
27
+ :clearable="true"
28
+ :loading="GeneratingStatus.includes(messageState.status)"
29
+ :showWordLimit="true"
30
+ :maxLength="1000"
31
+ :template="currentTemplate"
32
+ @submit="handleSendMessage"
33
+ @cancel="abortRequest"
34
+ @keydown="handleMessageKeydown($event, onTrigger, onKeyDown)"
35
+ @reset-template="clearTemplate"
36
+ ></tr-sender>
37
+ </div>
38
+ </template>
39
+ </tr-container>
40
+ </template>
41
+
42
+ <script setup lang="ts">
43
+ import { TrBubbleList, TrContainer, TrPrompts, TrSender, TrWelcome, TrSuggestionPills } from '@opentiny/tiny-robot'
44
+ import { GeneratingStatus } from '@opentiny/tiny-robot-kit'
45
+ import { useTinyRobot } from '../composable/useTinyRobot'
46
+ import { appData } from '../tools/appData'
47
+
48
+ const props = defineProps<{
49
+ promptItems: any[]
50
+ suggestionPillItems: any[]
51
+ }>()
52
+
53
+ const {
54
+ fullscreen,
55
+ welcomeIcon,
56
+ promptItems: defaultPromptItems,
57
+ messages,
58
+ messageState,
59
+ inputMessage,
60
+ abortRequest,
61
+ roles,
62
+ handlePromptItemClick,
63
+ senderRef,
64
+ currentTemplate,
65
+ clearTemplate,
66
+ handleSendMessage,
67
+ handleMessageKeydown,
68
+ suggestionPillItems: defaultSuggestionPillItems,
69
+ handleSuggestionPillItemClick
70
+ } = useTinyRobot()
71
+
72
+ const customPromptItems = props.promptItems || defaultPromptItems
73
+ const customSuggestionPillItems = props.suggestionPillItems || defaultSuggestionPillItems
74
+ </script>
75
+
76
+ <style scoped lang="less">
77
+ .chat-input {
78
+ margin-top: 8px;
79
+ padding: 10px 15px;
80
+ }
81
+
82
+ .tr-container {
83
+ top: 64px !important;
84
+
85
+ container-type: inline-size;
86
+
87
+ :deep(.tr-welcome__title-wrapper) {
88
+ display: flex;
89
+ align-items: center;
90
+ justify-content: center;
91
+ }
92
+ }
93
+
94
+ .welcome-footer {
95
+ margin-top: 12px;
96
+ color: rgb(128, 128, 128);
97
+ font-size: 12px;
98
+ line-height: 20px;
99
+ }
100
+
101
+ .tiny-prompts {
102
+ padding: 16px 24px;
103
+
104
+ :deep(.prompt-item) {
105
+ width: 100%;
106
+ box-sizing: border-box;
107
+
108
+ @container (width >=64rem) {
109
+ width: calc(50% - 8px);
110
+ }
111
+
112
+ .tr-prompt__content-label {
113
+ font-size: 14px;
114
+ line-height: 24px;
115
+ }
116
+ }
117
+ }
118
+
119
+ .tr-history-demo {
120
+ position: absolute;
121
+ right: 100%;
122
+ top: 100%;
123
+ z-index: 100;
124
+ width: 300px;
125
+ height: 600px;
126
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.04);
127
+ }
128
+ </style>
@@ -0,0 +1,65 @@
1
+ import type { ChatCompletionRequest } from '@opentiny/tiny-robot-kit'
2
+ import type { AIModelConfig } from '@opentiny/tiny-robot-kit'
3
+ import type { ChatCompletionResponse } from '@opentiny/tiny-robot-kit'
4
+ import type { StreamHandler } from '@opentiny/tiny-robot-kit'
5
+ import { BaseModelProvider } from '@opentiny/tiny-robot-kit'
6
+ import { globalConversation, handleSSEStream } from './utils.js'
7
+ import type { Ref } from 'vue'
8
+
9
+ /**
10
+ * 对接AIClient的自定义 Dify 大模型服务
11
+ *
12
+ * const client = new AIClient({
13
+ * provider: 'custom',
14
+ * providerImplementation: new CustomModelProvider( config )
15
+ * });
16
+ */
17
+ export class DifyModelProvider extends BaseModelProvider {
18
+ _messages: Ref<ChatCompletionRequest['messages']> = []
19
+
20
+ constructor(config: AIModelConfig) {
21
+ super(config)
22
+ }
23
+ /** 同步请示不需要实现 */
24
+ chat(request: ChatCompletionRequest): Promise<ChatCompletionResponse> {
25
+ throw new Error('Method not implemented.')
26
+ }
27
+
28
+ /** 异步流式请求 */
29
+ async chatStream(request: ChatCompletionRequest, handler: StreamHandler): Promise<void> {
30
+ const { signal } = request
31
+ this.validateRequest(request)
32
+
33
+ try {
34
+ // 验证请求的messages属性,必须是数组,且每个消息必须有role\content属性
35
+ const lastMessage = request.messages[request.messages.length - 1].content
36
+ // 模拟异步流式响应
37
+ const response = await fetch(`${this.config.apiUrl}/chat-messages`, {
38
+ method: 'POST',
39
+ headers: {
40
+ 'Content-Type': 'application/json',
41
+ 'Authorization': `Bearer ${this.config.apiKey}`,
42
+ 'Accept': 'text/event-stream'
43
+ },
44
+ body: JSON.stringify({
45
+ query: lastMessage,
46
+ user: 'user',
47
+ response_mode: 'streaming',
48
+ inputs: {
49
+ sessionId: globalConversation.sessionId
50
+ },
51
+ conversation_id: globalConversation.id
52
+ })
53
+ })
54
+
55
+ await handleSSEStream(response, handler, this._messages, signal)
56
+ } catch (error) {
57
+ if (signal && signal.aborted) {
58
+ console.warn('Request was aborted:', error)
59
+ } else {
60
+ console.error('Error in chatStream:', error)
61
+ // handler.onError(handleRequestError(error))
62
+ }
63
+ }
64
+ }
65
+ }
@@ -0,0 +1,71 @@
1
+ import { ref, watch } from 'vue'
2
+
3
+ function parse(str) {
4
+ if (str === null) return undefined
5
+ const type = str[0]
6
+ const strVal = str.slice(1)
7
+ // 对象时,有可能是Date, 否则反解析后统一是对象
8
+ if (type === 'o' || type === 'b') {
9
+ let val = JSON.parse(strVal)
10
+ return typeof val === 'string' ? new Date(val) : val
11
+ }
12
+ if (type === 'n') return +Number(strVal)
13
+ if (type === 's') return strVal
14
+ }
15
+
16
+ // 带前缀的保存值
17
+ function save(store, k, v) {
18
+ const type = typeof v
19
+ store.setItem(k, type[0] + (type === 'object' ? JSON.stringify(v) : v))
20
+ }
21
+
22
+ /**
23
+ * 快速的保存值到 sessionStorage, localStorage.
24
+ * 支持基本类型,时间,数组,对象,null,不存在的键值返回undefined 。
25
+ * 不支持:Map,Set, 以及多级串联赋值,比如:$session.obj.name="abcd"
26
+ */
27
+ function handler(storage) {
28
+ return {
29
+ get(target, propKey, receiver) {
30
+ return parse(storage.getItem(propKey))
31
+ },
32
+ set(target, propKey, value) {
33
+ save(storage, propKey, value)
34
+ return true
35
+ }
36
+ }
37
+ }
38
+
39
+ /** * 快速读写sessionStorage 示例: $session.abc="shen" */
40
+ const $session = new Proxy({}, handler(sessionStorage))
41
+
42
+ /** * 快速读写 localStorage 示例: $local.abc="shen" */
43
+ const $local = new Proxy({}, handler(localStorage))
44
+
45
+ /** * 全局共享值,刷新即丢失! 示例: $cache.abc="shen" */
46
+ const $cache = {}
47
+
48
+ const typeMatcher = { session: $session, local: $local, api: null }
49
+
50
+ /**
51
+ * 用于记录用户行为,并保存到session,local 或api接口(api保存的功能还未实现)
52
+ * 示例:useAutoStore("session","key1")
53
+ * useAutoStore("session","key2",100)
54
+ * useAutoStore("session","key2",$session.key2 || 100)
55
+ * @param type 自动存储到的目标
56
+ * @param key 存储时的key
57
+ * @param defaultValue 默认值。
58
+ * @returns 响应式ref
59
+ */
60
+ const useAutoStore = (type, key, defaultValue) => {
61
+ let refVar = ref(typeMatcher[type][key])
62
+ watch(refVar, (curr, prev) => {
63
+ typeMatcher[type][key] = curr
64
+ })
65
+
66
+ refVar.value = refVar.value ?? defaultValue
67
+
68
+ return refVar
69
+ }
70
+
71
+ export { $session, $local, $cache, useAutoStore }
@@ -0,0 +1,167 @@
1
+ import type { AIModelConfig } from '@opentiny/tiny-robot-kit'
2
+ import { AIClient, useConversation } from '@opentiny/tiny-robot-kit'
3
+ import { IconAi, IconUser } from '@opentiny/tiny-robot-svgs'
4
+ import { h, nextTick, onMounted, ref, watch } from 'vue'
5
+ import { DifyModelProvider } from './DifyModelProvider.js'
6
+ import type { SuggestionItem } from '@opentiny/tiny-robot'
7
+
8
+ const difyConfig: AIModelConfig = {
9
+ provider: 'custom',
10
+ apiUrl: 'https://api.dify.ai/v1',
11
+ apiKey: 'app-H0VJI4LqZ4KskdcA5a07pjXf'
12
+ }
13
+ export function useTinyRobot() {
14
+ const difyModelProvider = new DifyModelProvider(difyConfig)
15
+ const client = new AIClient({
16
+ providerImplementation: difyModelProvider,
17
+ ...difyConfig
18
+ })
19
+
20
+ const fullscreen = ref(false)
21
+ const show = ref(true)
22
+
23
+ const aiAvatar = h(IconAi, { style: { fontSize: '32px' } })
24
+ const userAvatar = h(IconUser, { style: { fontSize: '32px' } })
25
+ const welcomeIcon = h(IconAi, { style: { fontSize: '48px' } })
26
+
27
+ const promptItems = [
28
+ {
29
+ label: '识别网页的内容',
30
+ description: '公司人员表中员工最多和最少的公司,帮我统计一下!',
31
+ icon: h('span', { style: { fontSize: '18px' } }, '💡')
32
+ },
33
+ {
34
+ label: '智能操作网页',
35
+ description: '请帮我选中公司人员表中员工最多的公司',
36
+ icon: h('span', { style: { fontSize: '18px' } }, '🕹')
37
+ }
38
+ ]
39
+ const handlePromptItemClick = (ev, item) => {
40
+ sendMessage(item.description)
41
+ }
42
+
43
+ const { messageManager } = useConversation({ client })
44
+ const { messages, messageState, inputMessage, sendMessage, abortRequest } = messageManager
45
+ difyModelProvider._messages = messages
46
+
47
+ const roles = {
48
+ assistant: {
49
+ type: 'markdown',
50
+ placement: 'start',
51
+ avatar: aiAvatar,
52
+ maxWidth: '80%'
53
+ },
54
+ user: {
55
+ placement: 'end',
56
+ avatar: userAvatar,
57
+ maxWidth: '80%'
58
+ }
59
+ }
60
+
61
+ // 建议按钮组,设置对话的模板
62
+ const suggestionPillItems = [
63
+ {
64
+ id: '1',
65
+ text: '公司人员表',
66
+ icon: h('span', { style: { fontSize: '18px' } }, '🏢')
67
+ }
68
+ ]
69
+
70
+ function handleSuggestionPillItemClick(item: SuggestionItem) {
71
+ let templateText = `请对 [目标组件] ,执行 [操作]`
72
+ let currentInitialValue = { 目标组件: item.text, 操作: '' }
73
+
74
+ if (senderRef.value) {
75
+ senderRef.value.setTemplate(templateText, currentInitialValue)
76
+ }
77
+ }
78
+
79
+ const senderRef = ref(null)
80
+ const currentTemplate = ref('')
81
+ const suggestionOpen = ref(false)
82
+
83
+ // 清除当前指令
84
+ const clearTemplate = () => {
85
+ // 清空指令相关状态
86
+ currentTemplate.value = ''
87
+
88
+ // 确保重新聚焦到输入框
89
+ nextTick(() => {
90
+ senderRef.value?.focus()
91
+ })
92
+ }
93
+
94
+ // 发送消息
95
+ const handleSendMessage = () => {
96
+ sendMessage(inputMessage.value)
97
+
98
+ clearTemplate()
99
+ }
100
+
101
+ const handleMessageKeydown = (event, triggerFn, suggestionKeyDown) => {
102
+ // 如果指令面板已打开,交给 suggestion 组件处理键盘事件
103
+ if (suggestionOpen.value) {
104
+ suggestionKeyDown(event)
105
+ return
106
+ }
107
+
108
+ // 如果按下斜杠键并且不在指令编辑模式,触发指令面板
109
+ if (event.key === '/' && !currentTemplate.value) {
110
+ triggerFn({
111
+ text: '',
112
+ position: 0
113
+ })
114
+ }
115
+
116
+ // ESC 键清除当前指令
117
+ if (event.key === 'Escape' && currentTemplate.value) {
118
+ event.preventDefault()
119
+ clearTemplate()
120
+ }
121
+ }
122
+
123
+ watch(
124
+ () => inputMessage.value,
125
+ (value) => {
126
+ // 如果指令面板已打开,并且指令为空,关闭指令面板
127
+ if (suggestionOpen.value && value === '') {
128
+ suggestionOpen.value = false
129
+ }
130
+ }
131
+ )
132
+
133
+ // 页面加载完成后自动聚焦输入框
134
+ onMounted(() => {
135
+ setTimeout(() => {
136
+ senderRef.value?.focus()
137
+ }, 500)
138
+ })
139
+
140
+ return {
141
+ client,
142
+ fullscreen,
143
+ show,
144
+ aiAvatar,
145
+ userAvatar,
146
+ welcomeIcon,
147
+ promptItems,
148
+
149
+ messageManager,
150
+ messages,
151
+ messageState,
152
+ inputMessage,
153
+ sendMessage,
154
+ abortRequest,
155
+ roles,
156
+ handlePromptItemClick,
157
+
158
+ senderRef,
159
+ currentTemplate,
160
+ suggestionOpen,
161
+ clearTemplate,
162
+ handleSendMessage,
163
+ handleMessageKeydown,
164
+ suggestionPillItems,
165
+ handleSuggestionPillItemClick
166
+ }
167
+ }