@zipify/wysiwyg 3.4.1 → 3.5.0-ai-prototype
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/dist/cli.js +2 -2
- package/dist/wysiwyg.css +179 -0
- package/dist/wysiwyg.mjs +3027 -868
- package/example/ExampleApp.vue +5 -1
- package/example/aiAdapter.js +21 -0
- package/lib/Wysiwyg.vue +11 -2
- package/lib/assets/icons/loading.svg +11 -0
- package/lib/assets/icons/send.svg +3 -0
- package/lib/assets/icons/sparkles.svg +3 -0
- package/lib/components/base/TextArea.vue +108 -0
- package/lib/components/base/index.js +1 -0
- package/lib/components/floatingMenu/AiWidgetSuggestionItem.vue +74 -0
- package/lib/components/floatingMenu/FloatingMenuControl.vue +204 -0
- package/lib/components/floatingMenu/index.js +1 -0
- package/lib/components/toolbar/controls/aiComponent/AiControl.vue +185 -0
- package/lib/components/toolbar/controls/aiComponent/AiControlHeader.vue +28 -0
- package/lib/components/toolbar/controls/aiComponent/AiSuggestionItem.vue +74 -0
- package/lib/components/toolbar/controls/index.js +1 -0
- package/lib/components/toolbar/layouts/ToolbarDesktop.vue +5 -2
- package/lib/extensions/AiComponent.js +21 -0
- package/lib/extensions/index.js +5 -0
- package/package.json +2 -1
package/example/ExampleApp.vue
CHANGED
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
:page-blocks="pageBlocks"
|
|
43
43
|
:active="isActive"
|
|
44
44
|
:readonly="isReadonly"
|
|
45
|
+
:ai-adapter="aiAdapterRef"
|
|
45
46
|
@update-favorite-colors="updateFavoriteColors"
|
|
46
47
|
/>
|
|
47
48
|
<pre class="zw-content-structure" v-html="structurePreview" />
|
|
@@ -54,6 +55,7 @@ import { Wysiwyg } from '../lib/entryLib';
|
|
|
54
55
|
import { FONTS } from './fonts';
|
|
55
56
|
import { PRESETS, renderPresetVariable } from './presets';
|
|
56
57
|
import { PAGE_BLOCKS } from './pageBlocks';
|
|
58
|
+
import { aiAdapter } from './aiAdapter';
|
|
57
59
|
|
|
58
60
|
function getInitialContent() {
|
|
59
61
|
const data = sessionStorage.getItem('wswg-data');
|
|
@@ -75,6 +77,7 @@ export default {
|
|
|
75
77
|
},
|
|
76
78
|
|
|
77
79
|
setup() {
|
|
80
|
+
const aiAdapterRef = ref(aiAdapter);
|
|
78
81
|
const wswgRef = ref(null);
|
|
79
82
|
const content = ref(getInitialContent());
|
|
80
83
|
const presets = ref(PRESETS);
|
|
@@ -135,7 +138,8 @@ export default {
|
|
|
135
138
|
presets,
|
|
136
139
|
isActive,
|
|
137
140
|
pageBlocks,
|
|
138
|
-
isReadonly
|
|
141
|
+
isReadonly,
|
|
142
|
+
aiAdapterRef
|
|
139
143
|
};
|
|
140
144
|
}
|
|
141
145
|
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const aiAdapter = {
|
|
2
|
+
generateText: async ({ prompt, context, istruction }) => {
|
|
3
|
+
try {
|
|
4
|
+
const response = await fetch('https://mendelson-test.eu.ngrok.io/process-text', {
|
|
5
|
+
method: 'POST',
|
|
6
|
+
headers: {
|
|
7
|
+
'Content-Type': 'application/json'
|
|
8
|
+
},
|
|
9
|
+
body: JSON.stringify({ text: prompt, context })
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
if (!response.ok) {
|
|
13
|
+
throw new Error(`HTTP error: ${response.status}`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return await response.json();
|
|
17
|
+
} catch (error) {
|
|
18
|
+
console.error('An error occurred while sending the request:', error);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
};
|
package/lib/Wysiwyg.vue
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
:popup-mode="popupMode"
|
|
7
7
|
ref="toolbarRef"
|
|
8
8
|
/>
|
|
9
|
-
|
|
9
|
+
<FloatingMenuControl />
|
|
10
10
|
<EditorContent :editor="editor" />
|
|
11
11
|
</div>
|
|
12
12
|
</template>
|
|
@@ -22,6 +22,7 @@ import { ContextWindow, FavoriteColors, Storage } from './services';
|
|
|
22
22
|
import { Devices } from './enums';
|
|
23
23
|
import { outClick } from './directives';
|
|
24
24
|
import { Font } from './models';
|
|
25
|
+
import { FloatingMenuControl } from './components/floatingMenu';
|
|
25
26
|
|
|
26
27
|
const MIN_FONT_SIZE = 5;
|
|
27
28
|
const MAX_FONT_SIZE = 112;
|
|
@@ -31,7 +32,8 @@ export default {
|
|
|
31
32
|
|
|
32
33
|
components: {
|
|
33
34
|
Toolbar,
|
|
34
|
-
EditorContent
|
|
35
|
+
EditorContent,
|
|
36
|
+
FloatingMenuControl
|
|
35
37
|
},
|
|
36
38
|
|
|
37
39
|
directives: {
|
|
@@ -132,6 +134,12 @@ export default {
|
|
|
132
134
|
window: {
|
|
133
135
|
required: false,
|
|
134
136
|
default: () => window
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
aiAdapter: {
|
|
140
|
+
type: Object,
|
|
141
|
+
required: false,
|
|
142
|
+
default: null
|
|
135
143
|
}
|
|
136
144
|
},
|
|
137
145
|
|
|
@@ -180,6 +188,7 @@ export default {
|
|
|
180
188
|
basePresetClass: props.basePresetClass,
|
|
181
189
|
baseListClass: props.baseListClass,
|
|
182
190
|
deviceRef: toRef(props, 'device'),
|
|
191
|
+
aiComponent: props.aiAdapter,
|
|
183
192
|
pageBlocksRef: pageBlocks,
|
|
184
193
|
wrapperRef
|
|
185
194
|
})
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<svg xml:space="preserve" style="width:var(--zw-icon-width);height:var(--zw-icon-height)" viewBox="0 0 100 100">
|
|
2
|
+
<circle cx="6" cy="50" r="6" fill="var(--zw-icon-foreground)">
|
|
3
|
+
<animateTransform attributeName="transform" begin=".1" dur="1s" repeatCount="indefinite" type="translate" values="0 15 ; 0 -15; 0 15"/>
|
|
4
|
+
</circle>
|
|
5
|
+
<circle cx="30" cy="50" r="6" fill="var(--zw-icon-foreground)">
|
|
6
|
+
<animateTransform attributeName="transform" begin=".2" dur="1s" repeatCount="indefinite" type="translate" values="0 10 ; 0 -10; 0 10"/>
|
|
7
|
+
</circle>
|
|
8
|
+
<circle cx="54" cy="50" r="6" fill="var(--zw-icon-foreground)">
|
|
9
|
+
<animateTransform attributeName="transform" begin=".3" dur="1s" repeatCount="indefinite" type="translate" values="0 5 ; 0 -5; 0 5"/>
|
|
10
|
+
</circle>
|
|
11
|
+
</svg>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" style="width:var(--zw-icon-width);height:var(--zw-icon-height)" viewBox="0 0 24 24">
|
|
2
|
+
<path stroke="var(--zw-icon-foreground)" stroke-width="2" d="m7 10.2.4.8c.3.5.4.7.4 1 0 .3 0 .5-.4 1l-.4.8c-1.2 2.1-1.9 3.2-1.4 3.7.5.6 1.6 0 4-1l6.2-2.7c1.8-.8 2.7-1.1 2.7-1.8s-.9-1-2.7-1.8L9.5 7.4c-2.3-1-3.4-1.5-3.9-1-.5.6.2 1.7 1.4 3.8Z"/>
|
|
3
|
+
</svg>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="#9c6ade" style="width:var(--zw-icon-width);height:var(--zw-icon-height)" viewBox="0 0 20 20">
|
|
2
|
+
<path d="M12.643 7.61c.124-.376.186-.564.288-.599a.216.216 0 0 1 .138 0c.102.035.164.223.288.6.519 1.577.778 2.366 1.268 2.991.225.288.485.548.773.773.625.49 1.414.75 2.991 1.268.377.124.565.186.6.288a.217.217 0 0 1 0 .138c-.035.102-.223.164-.6.288-1.577.519-2.366.778-2.991 1.268a4.53 4.53 0 0 0-.773.773c-.49.625-.75 1.414-1.268 2.991-.124.377-.186.565-.288.6a.217.217 0 0 1-.138 0c-.102-.035-.164-.223-.288-.6-.519-1.577-.778-2.366-1.268-2.991a4.53 4.53 0 0 0-.773-.773c-.625-.49-1.414-.75-2.991-1.268-.377-.124-.565-.186-.6-.288a.216.216 0 0 1 0-.138c.035-.102.223-.164.6-.288 1.577-.519 2.366-.778 2.991-1.268a4.53 4.53 0 0 0 .773-.773c.49-.625.75-1.414 1.268-2.991ZM4.762 4.407c.083-.251.124-.377.192-.4.03-.01.062-.01.092 0 .068.023.11.149.192.4.346 1.052.519 1.578.845 1.994.15.192.324.365.516.516.416.326.942.5 1.994.845.251.083.377.124.4.192.01.03.01.062 0 .092-.023.068-.149.11-.4.192-1.052.346-1.578.519-1.994.845-.192.15-.365.324-.516.516-.326.416-.5.942-.845 1.994-.083.251-.124.377-.192.4a.144.144 0 0 1-.092 0c-.068-.023-.11-.149-.192-.4-.346-1.052-.519-1.578-.845-1.994a3.022 3.022 0 0 0-.516-.516c-.416-.326-.942-.5-1.994-.845-.251-.083-.377-.124-.4-.192a.144.144 0 0 1 0-.092c.023-.068.149-.11.4-.192 1.052-.346 1.578-.519 1.994-.845.192-.15.365-.324.516-.516.326-.416.5-.942.845-1.994Zm5.589-3.153c.052-.157.078-.235.12-.25a.09.09 0 0 1 .058 0c.042.015.068.093.12.25.216.658.324.986.528 1.247.094.12.202.228.322.322.26.204.59.312 1.247.528.156.052.235.078.25.12a.089.089 0 0 1 0 .058c-.015.042-.094.068-.25.12-.658.216-.987.324-1.247.528-.12.094-.228.202-.322.322-.204.26-.312.59-.528 1.247-.052.156-.078.235-.12.25a.09.09 0 0 1-.058 0c-.042-.015-.068-.094-.12-.25-.216-.658-.324-.986-.528-1.247a1.888 1.888 0 0 0-.322-.322c-.26-.204-.59-.312-1.247-.528-.156-.052-.235-.078-.25-.12a.09.09 0 0 1 0-.058c.015-.042.094-.068.25-.12.658-.216.986-.324 1.247-.528.12-.094.228-.202.322-.322.204-.26.312-.59.528-1.247Z"/>
|
|
3
|
+
</svg>
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="zw-field">
|
|
3
|
+
<label v-if="label" class="zw-field__label" :for="fieldId" data-test-selector="label">
|
|
4
|
+
{{ label }}
|
|
5
|
+
</label>
|
|
6
|
+
|
|
7
|
+
<textarea
|
|
8
|
+
class="zw-field__input"
|
|
9
|
+
:value="value"
|
|
10
|
+
:id="fieldId"
|
|
11
|
+
:placeholder="placeholder"
|
|
12
|
+
@input="onInput"
|
|
13
|
+
data-test-selector="input"
|
|
14
|
+
/>
|
|
15
|
+
|
|
16
|
+
<p class="zw-field__label--error" v-if="error" data-test-selector="error">
|
|
17
|
+
{{ error }}
|
|
18
|
+
</p>
|
|
19
|
+
</div>
|
|
20
|
+
</template>
|
|
21
|
+
|
|
22
|
+
<script>
|
|
23
|
+
import { computed } from 'vue';
|
|
24
|
+
|
|
25
|
+
export default {
|
|
26
|
+
name: 'TextArea',
|
|
27
|
+
|
|
28
|
+
props: {
|
|
29
|
+
value: {
|
|
30
|
+
type: [Number, String],
|
|
31
|
+
required: true
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
label: {
|
|
35
|
+
type: String,
|
|
36
|
+
required: false,
|
|
37
|
+
default: ''
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
placeholder: {
|
|
41
|
+
type: String,
|
|
42
|
+
required: false,
|
|
43
|
+
default: ''
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
error: {
|
|
47
|
+
type: String,
|
|
48
|
+
required: false,
|
|
49
|
+
default: null
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
setup(props, { emit }) {
|
|
54
|
+
const onInput = (event) => emit('input', event.target.value);
|
|
55
|
+
const fieldId = computed(() => {
|
|
56
|
+
return props.label.toLowerCase().replace(/[^a-zA-Z0-9]+(.)/g, (m, chr) => chr.toUpperCase());
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
return { onInput, fieldId };
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
</script>
|
|
63
|
+
|
|
64
|
+
<style scoped>
|
|
65
|
+
.zw-field {
|
|
66
|
+
display: flex;
|
|
67
|
+
flex-direction: column;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.zw-field__input {
|
|
71
|
+
--border-color: rgb(var(--zw-color-n60));
|
|
72
|
+
--text-color: rgb(var(--zw-color-n85));
|
|
73
|
+
|
|
74
|
+
border: 1px solid var(--border-color);
|
|
75
|
+
background-color: transparent;
|
|
76
|
+
color: var(--text-color);
|
|
77
|
+
font-size: var(--zw-font-size-xxs);
|
|
78
|
+
outline: none;
|
|
79
|
+
padding: 6px;
|
|
80
|
+
line-height: var(--zw-line-height-xxs);
|
|
81
|
+
transition: 0.1s border ease-out, 0.1s color ease-out;
|
|
82
|
+
will-change: border, color;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.zw-field__input:hover {
|
|
86
|
+
--border-color: rgb(var(--zw-color-n80));
|
|
87
|
+
--text-color: rgb(var(--zw-color-n85));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.zw-field__input:focus,
|
|
91
|
+
.zw-field__input:focus-within {
|
|
92
|
+
--border-color: rgb(var(--zw-color-white));
|
|
93
|
+
--text-color: rgb(var(--zw-color-white));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.zw-field__label {
|
|
97
|
+
display: inline-block;
|
|
98
|
+
font-size: var(--zw-font-size-xxs);
|
|
99
|
+
padding-bottom: var(--zw-offset-xxs);
|
|
100
|
+
line-height: var(--zw-line-height-xxs);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.zw-field__label--error {
|
|
104
|
+
font-size: var(--zw-font-size-xxs);
|
|
105
|
+
margin: var(--zw-offset-xxs) 0 0;
|
|
106
|
+
color: rgb(var(--zw-color-red));
|
|
107
|
+
}
|
|
108
|
+
</style>
|
|
@@ -7,6 +7,7 @@ export { default as Range } from './Range';
|
|
|
7
7
|
export { default as NumberField } from './NumberField';
|
|
8
8
|
export { default as Modal } from './Modal';
|
|
9
9
|
export { default as TextField } from './TextField';
|
|
10
|
+
export { default as TextArea } from './TextArea';
|
|
10
11
|
export { default as Checkbox } from './Checkbox';
|
|
11
12
|
export { useModalToggler, useElementRef } from './composables';
|
|
12
13
|
export * from './dropdown';
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="zw-ai-component__suggestion">
|
|
3
|
+
<p class="zw-ai-component-suggestion__request zw-margin-bottom--sm">
|
|
4
|
+
{{ suggestion.request }}
|
|
5
|
+
</p>
|
|
6
|
+
<p class="zw-margin-bottom--sm" v-html="suggestion.content" />
|
|
7
|
+
|
|
8
|
+
<Button v-if="suggestion.state !== 'loading'" @click="insert" class="zw-ai-component-suggestion__button">
|
|
9
|
+
Insert
|
|
10
|
+
</Button>
|
|
11
|
+
</div>
|
|
12
|
+
</template>
|
|
13
|
+
|
|
14
|
+
<script>
|
|
15
|
+
import { inject } from 'vue';
|
|
16
|
+
import { Button } from '../base';
|
|
17
|
+
import { InjectionTokens } from '../../injectionTokens';
|
|
18
|
+
|
|
19
|
+
export default {
|
|
20
|
+
name: 'AiWidgetSuggestionItem',
|
|
21
|
+
|
|
22
|
+
components: {
|
|
23
|
+
Button
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
props: {
|
|
27
|
+
suggestion: {
|
|
28
|
+
type: Object,
|
|
29
|
+
required: true
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
setup(props, { emit }) {
|
|
34
|
+
const editor = inject(InjectionTokens.EDITOR);
|
|
35
|
+
|
|
36
|
+
const insert = () => {
|
|
37
|
+
editor.chain().insertContent(props.suggestion.content).run();
|
|
38
|
+
emit('close');
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
insert
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
</script>
|
|
47
|
+
|
|
48
|
+
<style scoped>
|
|
49
|
+
.zw-ai-component__suggestion {
|
|
50
|
+
font-size: 14px;
|
|
51
|
+
margin-bottom: 8px;
|
|
52
|
+
padding: 16px 8px;
|
|
53
|
+
background-color: #FFF;
|
|
54
|
+
border: 0.5px solid #CDD1DC;
|
|
55
|
+
border-radius: 2px;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.zw-ai-component-suggestion__request {
|
|
59
|
+
font-size: 12px;
|
|
60
|
+
color: #666;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.zw-ai-component-suggestion__button {
|
|
64
|
+
font-size: 14px;
|
|
65
|
+
padding: 2px 4px;
|
|
66
|
+
background-color: #3AAA35;
|
|
67
|
+
color: #FFF;
|
|
68
|
+
border-radius: 2px;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.zw-ai-component-suggestion__button:hover {
|
|
72
|
+
opacity: 0.8;
|
|
73
|
+
}
|
|
74
|
+
</style>
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="zw-position--relative">
|
|
3
|
+
<FloatingMenu
|
|
4
|
+
:should-show="shouldShow"
|
|
5
|
+
v-if="editor"
|
|
6
|
+
:editor="editor"
|
|
7
|
+
ref="floatingMenu"
|
|
8
|
+
|
|
9
|
+
:tippy-options="menuOptions"
|
|
10
|
+
>
|
|
11
|
+
<Button icon class="zw-floating-menu__button" :active="isActive" @click="toggler.open" v-tooltip="'Write with AI'">
|
|
12
|
+
<Icon name="sparkles" size="24px" />
|
|
13
|
+
</Button>
|
|
14
|
+
</FloatingMenu>
|
|
15
|
+
|
|
16
|
+
<Modal class="zw-link-modal" :toggler="toggler" ref="modalRef" focus-first-control>
|
|
17
|
+
<form class="zw-link-modal__body" @submit.prevent="generateText">
|
|
18
|
+
<div class="zw-link-form__body">
|
|
19
|
+
Selected text:
|
|
20
|
+
<p class="zw-ai-component__selected-text">
|
|
21
|
+
{{ selectedText }}
|
|
22
|
+
</p>
|
|
23
|
+
|
|
24
|
+
<template v-if="suggestions.length || isLoading">
|
|
25
|
+
<p class="zw-margin-bottom--xs">
|
|
26
|
+
Suggestions:
|
|
27
|
+
</p>
|
|
28
|
+
<AiWidgetSuggestionItem
|
|
29
|
+
v-for="suggestion of suggestions"
|
|
30
|
+
:suggestion="suggestion"
|
|
31
|
+
:key="suggestion.result"
|
|
32
|
+
@close="toggler.close"
|
|
33
|
+
/>
|
|
34
|
+
|
|
35
|
+
<AiWidgetSuggestionItem v-if="isLoading" :suggestion="{ content: 'Working on it...', state: 'loading' }" />
|
|
36
|
+
</template>
|
|
37
|
+
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<div class="zw-position--relative">
|
|
41
|
+
<input v-model="prompt" type="text" class="zw-ai-component__input" placeholder="Tell us to ...">
|
|
42
|
+
|
|
43
|
+
<Button type="submit" class="zw-ai-component__send-button">
|
|
44
|
+
<Icon auto-color class="zw-ai-component__icon" :name="iconName" size="32px" />
|
|
45
|
+
</Button>
|
|
46
|
+
</div>
|
|
47
|
+
</form>
|
|
48
|
+
</Modal>
|
|
49
|
+
</div>
|
|
50
|
+
</template>
|
|
51
|
+
|
|
52
|
+
<script>
|
|
53
|
+
import { inject, ref, unref, computed } from 'vue';
|
|
54
|
+
import { FloatingMenu } from '@tiptap/vue-2';
|
|
55
|
+
import { Button, Icon, Modal, useModalToggler } from '../base';
|
|
56
|
+
import { InjectionTokens } from '../../injectionTokens';
|
|
57
|
+
import { tooltip } from '../../directives';
|
|
58
|
+
import AiWidgetSuggestionItem from './AiWidgetSuggestionItem';
|
|
59
|
+
|
|
60
|
+
export default {
|
|
61
|
+
name: 'FloatingMenuControl',
|
|
62
|
+
|
|
63
|
+
components: {
|
|
64
|
+
AiWidgetSuggestionItem,
|
|
65
|
+
FloatingMenu,
|
|
66
|
+
Icon,
|
|
67
|
+
Button,
|
|
68
|
+
Modal
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
directives: {
|
|
72
|
+
tooltip
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
setup() {
|
|
76
|
+
const editor = inject(InjectionTokens.EDITOR);
|
|
77
|
+
const suggestions = ref([]);
|
|
78
|
+
const wrapperRef = ref(null);
|
|
79
|
+
const floatingMenu = ref(null);
|
|
80
|
+
const modalRef = ref(null);
|
|
81
|
+
const prompt = ref('');
|
|
82
|
+
const isLoading = ref(false);
|
|
83
|
+
|
|
84
|
+
const selectedText = computed(() => editor.commands.getSelectedText());
|
|
85
|
+
|
|
86
|
+
const iconName = computed(() => {
|
|
87
|
+
return unref(isLoading) ? 'loading' : 'send';
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const onBeforeOpened = () => {
|
|
91
|
+
suggestions.value = [];
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const toggler = useModalToggler({
|
|
95
|
+
onBeforeOpened: () => onBeforeOpened(),
|
|
96
|
+
wrapperRef: floatingMenu,
|
|
97
|
+
modalRef
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const isActive = computed(() => unref(toggler.isOpened));
|
|
101
|
+
|
|
102
|
+
const shouldShow = () => {
|
|
103
|
+
return selectedText.value.length > 1;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const generateText = async () => {
|
|
107
|
+
if(isLoading.value) return;
|
|
108
|
+
|
|
109
|
+
isLoading.value = true;
|
|
110
|
+
const lastSuggestions = suggestions.value[suggestions.value.length - 1];
|
|
111
|
+
|
|
112
|
+
const context = lastSuggestions ? lastSuggestions.content : selectedText.value;
|
|
113
|
+
|
|
114
|
+
const result = await editor.commands.generateText({ prompt: prompt.value, context });
|
|
115
|
+
|
|
116
|
+
isLoading.value = false;
|
|
117
|
+
suggestions.value.push(result);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const menuOptions = {
|
|
121
|
+
placement: 'right-end',
|
|
122
|
+
offset: [0, 10]
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
selectedText,
|
|
127
|
+
editor,
|
|
128
|
+
floatingMenu,
|
|
129
|
+
shouldShow,
|
|
130
|
+
menuOptions,
|
|
131
|
+
toggler,
|
|
132
|
+
wrapperRef,
|
|
133
|
+
modalRef,
|
|
134
|
+
isActive,
|
|
135
|
+
generateText,
|
|
136
|
+
suggestions,
|
|
137
|
+
prompt,
|
|
138
|
+
iconName,
|
|
139
|
+
isLoading
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
</script>
|
|
144
|
+
|
|
145
|
+
<style scoped>
|
|
146
|
+
.zw-floating-menu__button {
|
|
147
|
+
padding: 2px;
|
|
148
|
+
background-color: rgba(255, 252, 252, 1);
|
|
149
|
+
border-radius: 50%;
|
|
150
|
+
box-shadow: 0 0 0 0 1px #878DA2;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.zw-link-modal {
|
|
154
|
+
width: 450px;
|
|
155
|
+
background-color: #FCFCFC;
|
|
156
|
+
border-radius: 8px;
|
|
157
|
+
box-shadow: 0 0 0 0.5px #878DA2, 0 0 2px 0.5px rgba(135, 141, 162, 0.5), 0 1px 8px 0.5px rgba(135, 141, 162, 0.1), 0 2px 12px 0.5px rgba(135, 141, 162, 0.1), 0 4px 20 0.5px rgba(135, 141, 162, 0.25);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.zw-link-modal__body {
|
|
161
|
+
padding: var(--zw-offset-sm);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.zw-link-form__body {
|
|
165
|
+
overscroll-behavior: contain;
|
|
166
|
+
overflow: auto;
|
|
167
|
+
max-height: max(min(calc(1051.21px - calc(calc(3.5px * 16) + 47.9844px) - calc(0.25px * 16)), calc(34.75px * 16)), calc(10px * 16));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.zw-ai-component__selected-text {
|
|
171
|
+
font-size: 14px;
|
|
172
|
+
margin-bottom: 8px;
|
|
173
|
+
padding: 16px 8px;
|
|
174
|
+
background-color: #FFF;
|
|
175
|
+
border: 0.5px solid #000;
|
|
176
|
+
border-radius: 2px;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.zw-ai-component__input {
|
|
180
|
+
width: 100%;
|
|
181
|
+
min-height: 40px;
|
|
182
|
+
border: 1px solid #E9E9E9;
|
|
183
|
+
border-radius: 5px;
|
|
184
|
+
padding: 8px;
|
|
185
|
+
font-size: 14px;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.zw-ai-component__send-button {
|
|
189
|
+
position: absolute;
|
|
190
|
+
top: 50%;
|
|
191
|
+
transform: translateY(-50%);
|
|
192
|
+
right: 16px;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.zw-ai-component__icon {
|
|
196
|
+
color: #CDD1DC;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.zw-ai-component__send-button:hover .zw-ai-component__icon {
|
|
200
|
+
color: #878DA2;
|
|
201
|
+
transition: color 0.1s ease-in-out;
|
|
202
|
+
}
|
|
203
|
+
</style>
|
|
204
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as FloatingMenuControl } from './FloatingMenuControl';
|