@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.
- package/demos/apis/dialog-select.js +42 -0
- package/demos/apis/popeditor.js +14 -0
- package/demos/apis/steps.js +15 -0
- package/demos/apis/text-popup.js +2 -2
- package/demos/apis/user-head.js +7 -18
- package/demos/mobile-first/app/pager/webdoc/pager.js +0 -48
- package/demos/mobile-first/app/steps/vertical.vue +14 -1
- package/demos/pc/app/calendar-view/calendar-event.spec.ts +2 -2
- package/demos/pc/app/date-panel/basic-usage.spec.ts +6 -6
- package/demos/pc/app/date-panel/custom-week.spec.ts +1 -1
- package/demos/pc/app/date-panel/disabled-date.spec.ts +8 -9
- package/demos/pc/app/date-panel/event.spec.ts +4 -4
- package/demos/pc/app/date-panel/format.spec.ts +2 -2
- package/demos/pc/app/date-panel/readonly.spec.ts +3 -3
- package/demos/pc/app/date-panel/unlink-panels.spec.ts +4 -4
- package/demos/pc/app/date-picker/basic-usage.spec.ts +2 -2
- package/demos/pc/app/date-picker/date-range.spec.ts +3 -3
- package/demos/pc/app/dialog-select/nest-grid-multi-composition-api.vue +19 -1
- package/demos/pc/app/dialog-select/nest-grid-multi.spec.ts +19 -0
- package/demos/pc/app/dialog-select/nest-grid-multi.vue +17 -1
- package/demos/pc/app/dialog-select/webdoc/dialog-select.js +2 -2
- package/demos/pc/app/grid/base/basic-usage-composition-api.vue +48 -93
- package/demos/pc/app/grid/base/basic-usage.spec.js +1 -1
- package/demos/pc/app/grid/base/basic-usage.vue +29 -132
- package/demos/pc/app/grid/dynamically-columns/dynamically-columns.spec.js +3 -3
- package/demos/pc/app/grid/webdoc/grid-ai-agent.js +23 -0
- package/demos/pc/app/popeditor/condition-layout-composition-api.vue +1 -0
- package/demos/pc/app/popeditor/condition-layout.spec.ts +1 -0
- package/demos/pc/app/popeditor/condition-layout.vue +1 -0
- package/demos/pc/app/popeditor/webdoc/popeditor.js +2 -2
- package/demos/pc/app/qr-code/style-composition-api.vue +14 -3
- package/demos/pc/app/qr-code/style.vue +15 -3
- package/demos/pc/app/text-popup/{value.spec.ts → modelValue.spec.ts} +2 -2
- package/demos/pc/app/text-popup/webdoc/text-popup.js +8 -8
- package/demos/pc/webdoc/changelog.md +452 -452
- package/package.json +27 -19
- package/playground/App.vue +2 -2
- package/src/App.vue +18 -1
- package/src/{views/components-doc/components → components}/demo.vue +1 -1
- package/src/{views/components-doc/components → components}/float-settings.vue +24 -7
- package/src/components/mcp-docs.vue +55 -0
- package/src/components/tiny-robot-chat.vue +128 -0
- package/src/composable/DifyModelProvider.ts +65 -0
- package/src/composable/storage.ts +71 -0
- package/src/composable/useTinyRobot.ts +167 -0
- package/src/composable/utils.ts +172 -0
- package/src/i18n/index.js +5 -2
- package/src/main.js +10 -1
- package/src/router.js +9 -0
- package/src/tools/appData.js +12 -2
- package/src/views/components-doc/common.vue +22 -8
- package/src/views/comprehensive/Demo.vue +211 -0
- package/src/views/comprehensive/index.vue +391 -0
- package/src/views/comprehensive/products.json +99 -0
- package/src/views/comprehensive/types/index.ts +37 -0
- package/src/views/layout/layout.vue +2 -2
- package/demos/mobile-first/app/pager/current-change.vue +0 -34
- package/demos/mobile-first/app/pager/next-click.vue +0 -34
- package/demos/mobile-first/app/pager/prev-click.vue +0 -34
- /package/demos/pc/app/text-popup/{clear-value-composition-api.vue → clear-modelValue-composition-api.vue} +0 -0
- /package/demos/pc/app/text-popup/{clear-value.spec.ts → clear-modelValue.spec.ts} +0 -0
- /package/demos/pc/app/text-popup/{clear-value.vue → clear-modelValue.vue} +0 -0
- /package/demos/pc/app/text-popup/{value-composition-api.vue → modelValue-composition-api.vue} +0 -0
- /package/demos/pc/app/text-popup/{value.vue → modelValue.vue} +0 -0
- /package/src/{views/components-doc/components → components}/anchor.vue +0 -0
- /package/src/{views/components-doc/components → components}/api-docs.vue +0 -0
- /package/src/{views/components-doc/components → components}/async-highlight.vue +0 -0
- /package/src/{views/components-doc/components → components}/contributor.vue +0 -0
- /package/src/{views/components-doc/components → components}/header.vue +0 -0
- /package/src/{views/components-doc/components → components}/version-tip.vue +0 -0
- /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.
|
|
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.
|
|
26
|
-
"@opentiny/vue": "~3.
|
|
27
|
-
"@opentiny/vue
|
|
28
|
-
"@opentiny/vue-design-
|
|
29
|
-
"@opentiny/vue-design-smb": "~3.
|
|
30
|
-
"@opentiny/vue-directive": "~3.
|
|
31
|
-
"@opentiny/vue-
|
|
32
|
-
"@opentiny/vue-
|
|
33
|
-
"@opentiny/vue-
|
|
34
|
-
"@opentiny/vue-
|
|
35
|
-
"@opentiny/vue-
|
|
36
|
-
"@opentiny/vue-icon-saas": "~3.
|
|
37
|
-
"@opentiny/vue-
|
|
38
|
-
"@opentiny/vue-
|
|
39
|
-
"@opentiny/vue-theme": "~3.
|
|
40
|
-
"@opentiny/vue-
|
|
41
|
-
"@opentiny/vue-modal": "~3.
|
|
42
|
-
"@opentiny/vue-theme
|
|
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",
|
package/playground/App.vue
CHANGED
|
@@ -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.
|
|
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.
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
+
}
|