@zipify/wysiwyg 3.5.1-ai-prototype → 3.5.3-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 +1 -1
- package/dist/wysiwyg.css +28 -20
- package/dist/wysiwyg.mjs +775 -523
- package/example/aiAdapter.js +32 -2
- package/lib/components/floatingMenu/FloatingMenuControl.vue +20 -2
- package/lib/components/toolbar/controls/aiComponent/AiControl.vue +39 -21
- package/lib/components/toolbar/controls/aiComponent/AiSettings.vue +153 -0
- package/lib/components/toolbar/controls/aiComponent/AiSuggestionItem.vue +15 -5
- package/lib/extensions/AiComponent.js +6 -5
- package/package.json +1 -1
package/example/aiAdapter.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
export const aiAdapter = {
|
|
2
|
-
generateText: async ({ prompt, context,
|
|
2
|
+
generateText: async ({ prompt, context, instructions }) => {
|
|
3
3
|
try {
|
|
4
4
|
const response = await fetch('https://mendelson-test.eu.ngrok.io/process-text', {
|
|
5
5
|
method: 'POST',
|
|
6
6
|
headers: {
|
|
7
7
|
'Content-Type': 'application/json'
|
|
8
8
|
},
|
|
9
|
-
body: JSON.stringify({ text: prompt, context })
|
|
9
|
+
body: JSON.stringify({ text: prompt, context, instructions })
|
|
10
10
|
});
|
|
11
11
|
|
|
12
12
|
if (!response.ok) {
|
|
@@ -18,5 +18,35 @@ export const aiAdapter = {
|
|
|
18
18
|
// eslint-disable-next-line no-console
|
|
19
19
|
console.error('An error occurred while sending the request:', error);
|
|
20
20
|
}
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
settings: {
|
|
24
|
+
tones: [
|
|
25
|
+
{ id: 'expert', title: 'Expert' },
|
|
26
|
+
{ id: 'daring', title: 'Daring' },
|
|
27
|
+
{ id: 'playful', title: 'Playful' },
|
|
28
|
+
{ id: 'sophisticated', title: 'Sophisticated' },
|
|
29
|
+
{ id: 'Persuasive', title: 'Persuasive' },
|
|
30
|
+
{ id: 'supportive', title: 'Supportive' },
|
|
31
|
+
{ id: 'personable', title: 'Personable' },
|
|
32
|
+
{ id: 'direct', title: 'Direct' },
|
|
33
|
+
{ id: 'empathetic', title: 'Empathetic' },
|
|
34
|
+
{ id: 'engaging', title: 'Engaging' },
|
|
35
|
+
{ id: 'neutral', title: 'Neutral' }
|
|
36
|
+
],
|
|
37
|
+
textStyles: [
|
|
38
|
+
{ id: 'formal', title: 'Formal' },
|
|
39
|
+
{ id: 'informal', title: 'Informal' },
|
|
40
|
+
{ id: 'technical', title: 'Technical' },
|
|
41
|
+
{ id: 'persuasive', title: 'Persuasive' },
|
|
42
|
+
{ id: 'promotional', title: 'Promotional' },
|
|
43
|
+
{ id: 'product description', title: 'Product description' },
|
|
44
|
+
{ id: 'befits', title: 'Befits' }
|
|
45
|
+
],
|
|
46
|
+
textLength: [
|
|
47
|
+
{ id: '200', title: 'Short (200)' },
|
|
48
|
+
{ id: '500', title: 'Medium (500)' },
|
|
49
|
+
{ id: '1000', title: 'Long (1000)' }
|
|
50
|
+
]
|
|
21
51
|
}
|
|
22
52
|
};
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
</template>
|
|
51
51
|
|
|
52
52
|
<script>
|
|
53
|
-
import { inject, ref, unref, computed } from 'vue';
|
|
53
|
+
import { inject, ref, unref, computed, watch } from 'vue';
|
|
54
54
|
import { FloatingMenu } from '@tiptap/vue-2';
|
|
55
55
|
import { Button, Icon, Modal, useModalToggler } from '../base';
|
|
56
56
|
import { InjectionTokens } from '../../injectionTokens';
|
|
@@ -83,6 +83,20 @@ export default {
|
|
|
83
83
|
|
|
84
84
|
const selectedText = computed(() => editor.commands.getSelectedText());
|
|
85
85
|
|
|
86
|
+
const settings = ref({
|
|
87
|
+
tone: '',
|
|
88
|
+
textLength: '500',
|
|
89
|
+
textStyle: ''
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
if (localStorage.getItem('ai-settings')) {
|
|
93
|
+
settings.value = JSON.parse(localStorage.getItem('ai-settings'));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
watch(settings, () => {
|
|
97
|
+
localStorage.setItem('ai-settings', JSON.stringify(settings.value));
|
|
98
|
+
});
|
|
99
|
+
|
|
86
100
|
const iconName = computed(() => {
|
|
87
101
|
return unref(isLoading) ? 'loading' : 'send';
|
|
88
102
|
});
|
|
@@ -111,7 +125,11 @@ export default {
|
|
|
111
125
|
|
|
112
126
|
const context = lastSuggestions ? lastSuggestions.content : selectedText.value;
|
|
113
127
|
|
|
114
|
-
const result = await editor.commands.generateText({
|
|
128
|
+
const result = await editor.commands.generateText({
|
|
129
|
+
prompt: prompt.value,
|
|
130
|
+
instructions: unref(settings),
|
|
131
|
+
context
|
|
132
|
+
});
|
|
115
133
|
|
|
116
134
|
isLoading.value = false;
|
|
117
135
|
suggestions.value.push(result);
|
|
@@ -5,18 +5,15 @@
|
|
|
5
5
|
</Button>
|
|
6
6
|
|
|
7
7
|
<Modal class="zw-suggestion-modal" :toggler="toggler" ref="modalRef" focus-first-control>
|
|
8
|
-
|
|
9
8
|
<AiControlHeader />
|
|
10
9
|
<form class="zw-link-modal__body" @submit.prevent="generateText">
|
|
11
10
|
|
|
12
|
-
<
|
|
13
|
-
v-if="isLoading"
|
|
14
|
-
:suggestion="{ content: 'Working on it...', state: 'loading' }"
|
|
15
|
-
/>
|
|
11
|
+
<Icon v-if="isLoading" auto-color class="zw-ai-component__icon" name="loading" size="32px" />
|
|
16
12
|
|
|
17
13
|
<AiSuggestionItem
|
|
18
14
|
v-else-if="lastSuggestions"
|
|
19
15
|
:suggestion="lastSuggestions"
|
|
16
|
+
@action="onAction"
|
|
20
17
|
/>
|
|
21
18
|
|
|
22
19
|
<label class="zw-field__label">
|
|
@@ -24,32 +21,30 @@
|
|
|
24
21
|
</label>
|
|
25
22
|
|
|
26
23
|
<div class="zw-position--relative">
|
|
27
|
-
<TextArea
|
|
28
|
-
class="zw-margin-bottom--sm"
|
|
29
|
-
v-model="prompt"
|
|
30
|
-
/>
|
|
24
|
+
<TextArea class="zw-margin-bottom--sm" v-model="prompt" />
|
|
31
25
|
<Button type="submit" class="zw-ai-component__send-button">
|
|
32
26
|
<Icon auto-color class="zw-ai-component__icon" name="send" size="32px" />
|
|
33
27
|
</Button>
|
|
34
28
|
</div>
|
|
35
29
|
|
|
30
|
+
<AiSettings v-model="settings" />
|
|
31
|
+
|
|
36
32
|
<Button :disabled="!lastSuggestions" type="button" skin="primary" @click="applyText">
|
|
37
33
|
Apply
|
|
38
34
|
</Button>
|
|
39
35
|
</form>
|
|
40
|
-
|
|
41
|
-
|
|
42
36
|
</Modal>
|
|
43
37
|
</div>
|
|
44
38
|
</template>
|
|
45
39
|
|
|
46
40
|
<script>
|
|
47
|
-
import { computed, ref, inject, unref } from 'vue';
|
|
41
|
+
import { computed, ref, inject, unref, watch } from 'vue';
|
|
48
42
|
import { InjectionTokens } from '../../../../injectionTokens';
|
|
49
43
|
import { tooltip } from '../../../../directives';
|
|
50
44
|
import { Button, Icon, Modal, TextArea, useModalToggler } from '../../../base';
|
|
51
45
|
import AiControlHeader from './AiControlHeader';
|
|
52
46
|
import AiSuggestionItem from './AiSuggestionItem';
|
|
47
|
+
import AiSettings from './AiSettings';
|
|
53
48
|
|
|
54
49
|
export default {
|
|
55
50
|
name: 'AiControl',
|
|
@@ -60,7 +55,8 @@ export default {
|
|
|
60
55
|
Button,
|
|
61
56
|
TextArea,
|
|
62
57
|
AiControlHeader,
|
|
63
|
-
AiSuggestionItem
|
|
58
|
+
AiSuggestionItem,
|
|
59
|
+
AiSettings
|
|
64
60
|
},
|
|
65
61
|
|
|
66
62
|
directives: {
|
|
@@ -75,6 +71,20 @@ export default {
|
|
|
75
71
|
const suggestions = ref([]);
|
|
76
72
|
const isLoading = ref(false);
|
|
77
73
|
|
|
74
|
+
const settings = ref({
|
|
75
|
+
tone: '',
|
|
76
|
+
textLength: '500',
|
|
77
|
+
textStyle: ''
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
if (localStorage.getItem('ai-settings')) {
|
|
81
|
+
settings.value = JSON.parse(localStorage.getItem('ai-settings'));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
watch(settings, () => {
|
|
85
|
+
localStorage.setItem('ai-settings', JSON.stringify(settings.value));
|
|
86
|
+
});
|
|
87
|
+
|
|
78
88
|
const textAreaLabel = computed(() => {
|
|
79
89
|
return suggestions.value.length ? 'Tell AI what to do next…' : 'Write you request';
|
|
80
90
|
});
|
|
@@ -83,7 +93,7 @@ export default {
|
|
|
83
93
|
|
|
84
94
|
const editor = inject(InjectionTokens.EDITOR);
|
|
85
95
|
|
|
86
|
-
const onBeforeOpened = () => {};
|
|
96
|
+
const onBeforeOpened = () => { };
|
|
87
97
|
|
|
88
98
|
const toggler = useModalToggler({
|
|
89
99
|
onBeforeOpened: () => onBeforeOpened(),
|
|
@@ -97,23 +107,29 @@ export default {
|
|
|
97
107
|
const lastSuggestions = suggestions.value[suggestions.value.length - 1];
|
|
98
108
|
|
|
99
109
|
const context = lastSuggestions ? lastSuggestions.content : '';
|
|
100
|
-
|
|
101
|
-
|
|
110
|
+
const result = await editor.commands.generateText({
|
|
111
|
+
prompt: action,
|
|
112
|
+
context,
|
|
113
|
+
instructions: unref(settings)
|
|
114
|
+
});
|
|
102
115
|
|
|
103
116
|
isLoading.value = false;
|
|
104
117
|
suggestions.value.push(result);
|
|
105
118
|
};
|
|
106
119
|
|
|
107
120
|
const generateText = async () => {
|
|
108
|
-
if(isLoading.value) return;
|
|
121
|
+
if (isLoading.value) return;
|
|
109
122
|
|
|
110
123
|
isLoading.value = true;
|
|
111
124
|
|
|
112
125
|
const lastSuggestions = suggestions.value[suggestions.value.length - 1];
|
|
113
126
|
|
|
114
127
|
const context = lastSuggestions ? lastSuggestions.content : '';
|
|
115
|
-
|
|
116
|
-
|
|
128
|
+
const result = await editor.commands.generateText({
|
|
129
|
+
prompt: prompt.value,
|
|
130
|
+
context,
|
|
131
|
+
instructions: unref(settings)
|
|
132
|
+
});
|
|
117
133
|
|
|
118
134
|
isLoading.value = false;
|
|
119
135
|
prompt.value = '';
|
|
@@ -121,7 +137,8 @@ export default {
|
|
|
121
137
|
};
|
|
122
138
|
|
|
123
139
|
const applyText = () => {
|
|
124
|
-
editor.commands.
|
|
140
|
+
editor.commands.clearContent(true);
|
|
141
|
+
editor.commands.insertContent(lastSuggestions.value.content);
|
|
125
142
|
toggler.close();
|
|
126
143
|
suggestions.value = [];
|
|
127
144
|
prompt.value = '';
|
|
@@ -140,7 +157,8 @@ export default {
|
|
|
140
157
|
textAreaLabel,
|
|
141
158
|
isLoading,
|
|
142
159
|
onAction,
|
|
143
|
-
applyText
|
|
160
|
+
applyText,
|
|
161
|
+
settings
|
|
144
162
|
};
|
|
145
163
|
}
|
|
146
164
|
};
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<h4>Settings</h4>
|
|
4
|
+
<div class="zw-selectors ">
|
|
5
|
+
<div class="zw-ai__dropdown zw-margin-right--xs">
|
|
6
|
+
<FieldLabel class="zw-margin-bottom--xxs">
|
|
7
|
+
Tone
|
|
8
|
+
</FieldLabel>
|
|
9
|
+
|
|
10
|
+
<Dropdown
|
|
11
|
+
class="zw-margin-bottom--sm"
|
|
12
|
+
color="gray"
|
|
13
|
+
:value="settings.tone"
|
|
14
|
+
:options="tones"
|
|
15
|
+
@change="changeTone"
|
|
16
|
+
>
|
|
17
|
+
<template #option="{ option }">
|
|
18
|
+
<DropdownOption
|
|
19
|
+
class="zw-link-modal-dropdown__option"
|
|
20
|
+
:option="option"
|
|
21
|
+
/>
|
|
22
|
+
</template>
|
|
23
|
+
</Dropdown>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<div class="zw-ai__dropdown zw-margin-right--xs ">
|
|
27
|
+
<FieldLabel class="zw-margin-bottom--xxs">
|
|
28
|
+
Text Style
|
|
29
|
+
</FieldLabel>
|
|
30
|
+
|
|
31
|
+
<Dropdown
|
|
32
|
+
class="zw-margin-bottom--sm"
|
|
33
|
+
color="gray"
|
|
34
|
+
:value="settings.textStyle"
|
|
35
|
+
:options="textStyles"
|
|
36
|
+
@change="changeTextStyle"
|
|
37
|
+
>
|
|
38
|
+
<template #option="{ option }">
|
|
39
|
+
<DropdownOption
|
|
40
|
+
class="zw-link-modal-dropdown__option"
|
|
41
|
+
:option="option"
|
|
42
|
+
/>
|
|
43
|
+
</template>
|
|
44
|
+
</Dropdown>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<div class="zw-ai__dropdown">
|
|
48
|
+
<FieldLabel class="zw-margin-bottom--xxs">
|
|
49
|
+
Length
|
|
50
|
+
</FieldLabel>
|
|
51
|
+
|
|
52
|
+
<Dropdown
|
|
53
|
+
class="zw-margin-bottom--sm"
|
|
54
|
+
color="gray"
|
|
55
|
+
:value="settings.textLength"
|
|
56
|
+
:options="textLength"
|
|
57
|
+
@change="changeTextLength"
|
|
58
|
+
>
|
|
59
|
+
<template #option="{ option }">
|
|
60
|
+
<DropdownOption
|
|
61
|
+
class="zw-link-modal-dropdown__option"
|
|
62
|
+
:option="option"
|
|
63
|
+
/>
|
|
64
|
+
</template>
|
|
65
|
+
</Dropdown>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
<Checkbox
|
|
69
|
+
class="zw-margin-bottom--sm"
|
|
70
|
+
label="Use block context"
|
|
71
|
+
:value="settings.useBlockContext"
|
|
72
|
+
@input="changUseBlockContext"
|
|
73
|
+
/>
|
|
74
|
+
</div>
|
|
75
|
+
</template>
|
|
76
|
+
|
|
77
|
+
<script>
|
|
78
|
+
import { computed, inject } from 'vue';
|
|
79
|
+
import { FieldLabel, Dropdown, DropdownOption, Checkbox } from '../../../base';
|
|
80
|
+
import { InjectionTokens } from '../../../../injectionTokens';
|
|
81
|
+
|
|
82
|
+
export default {
|
|
83
|
+
name: 'AiSettings',
|
|
84
|
+
|
|
85
|
+
components: {
|
|
86
|
+
FieldLabel,
|
|
87
|
+
Dropdown,
|
|
88
|
+
DropdownOption,
|
|
89
|
+
Checkbox
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
props: {
|
|
93
|
+
value: {
|
|
94
|
+
type: Object,
|
|
95
|
+
default: () => ({})
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
setup(props, { emit }) {
|
|
100
|
+
const settings = computed(() => {
|
|
101
|
+
return {
|
|
102
|
+
tone: props.value.tone,
|
|
103
|
+
textStyle: props.value.textStyle,
|
|
104
|
+
textLength: props.value.textLength,
|
|
105
|
+
useBlockContext: props.value.useBlockContext
|
|
106
|
+
};
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const editor = inject(InjectionTokens.EDITOR);
|
|
110
|
+
const tones = editor.commands.getAiSettings().tones;
|
|
111
|
+
const textStyles = editor.commands.getAiSettings().textStyles;
|
|
112
|
+
const textLength = editor.commands.getAiSettings().textLength;
|
|
113
|
+
|
|
114
|
+
const changeTone = (value) => {
|
|
115
|
+
settings.value.tone = value;
|
|
116
|
+
emit('input', settings.value);
|
|
117
|
+
};
|
|
118
|
+
const changeTextStyle = (value) => {
|
|
119
|
+
settings.value.textStyle = value;
|
|
120
|
+
emit('input', settings.value);
|
|
121
|
+
};
|
|
122
|
+
const changeTextLength = (value) => {
|
|
123
|
+
settings.value.textLength = value;
|
|
124
|
+
emit('input', settings.value);
|
|
125
|
+
};
|
|
126
|
+
const changUseBlockContext = (value) => {
|
|
127
|
+
settings.value.useBlockContext = value;
|
|
128
|
+
emit('input', settings.value);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
tones,
|
|
133
|
+
textStyles,
|
|
134
|
+
textLength,
|
|
135
|
+
changeTone,
|
|
136
|
+
changeTextStyle,
|
|
137
|
+
changeTextLength,
|
|
138
|
+
changUseBlockContext,
|
|
139
|
+
settings
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
</script>
|
|
144
|
+
|
|
145
|
+
<style scoped>
|
|
146
|
+
.zw-selectors {
|
|
147
|
+
display: flex;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.zw-ai__dropdown {
|
|
151
|
+
width: 100%;
|
|
152
|
+
}
|
|
153
|
+
</style>
|
|
@@ -5,16 +5,24 @@
|
|
|
5
5
|
</p>
|
|
6
6
|
<p :class="{'zw-margin-bottom--sm': !isLoading }" v-html="suggestion.content" />
|
|
7
7
|
|
|
8
|
-
<Button type="button" v-if="!isLoading" @click="sendAction('Rewrite text')" class="zw-ai-component-suggestion__button">
|
|
9
|
-
|
|
8
|
+
<Button type="button" v-if="!isLoading" @click="sendAction($event, 'Rewrite text')" class="zw-ai-component-suggestion__button">
|
|
9
|
+
Rewrite
|
|
10
|
+
</Button>
|
|
11
|
+
<Button type="button" v-if="!isLoading" @click="sendAction($event, 'Simplify it')" class="zw-ai-component-suggestion__button">
|
|
12
|
+
Simplify it
|
|
13
|
+
</Button>
|
|
14
|
+
<Button type="button" v-if="!isLoading" @click="sendAction($event, 'Shorten it')" class="zw-ai-component-suggestion__button">
|
|
15
|
+
Shorten it
|
|
16
|
+
</Button>
|
|
17
|
+
<Button type="button" v-if="!isLoading" @click="sendAction($event, 'Make it exciting')" class="zw-ai-component-suggestion__button">
|
|
18
|
+
Make it exciting
|
|
10
19
|
</Button>
|
|
11
20
|
</div>
|
|
12
21
|
</template>
|
|
13
22
|
|
|
14
23
|
<script>
|
|
15
|
-
import {
|
|
24
|
+
import { computed } from 'vue';
|
|
16
25
|
import { Button } from '../../../base';
|
|
17
|
-
import { InjectionTokens } from '../../../../injectionTokens';
|
|
18
26
|
|
|
19
27
|
export default {
|
|
20
28
|
name: 'AiSuggestionItem',
|
|
@@ -33,7 +41,8 @@ export default {
|
|
|
33
41
|
setup(props, { emit }) {
|
|
34
42
|
const isLoading = computed(() => props.suggestion.state === 'loading');
|
|
35
43
|
|
|
36
|
-
const sendAction = (action) => {
|
|
44
|
+
const sendAction = (event, action) => {
|
|
45
|
+
event.stopPropagation();
|
|
37
46
|
emit('action', action);
|
|
38
47
|
};
|
|
39
48
|
|
|
@@ -65,6 +74,7 @@ export default {
|
|
|
65
74
|
background-color: #A4A4A4;
|
|
66
75
|
color: #FFF;
|
|
67
76
|
border-radius: 2px;
|
|
77
|
+
margin-right: 8px;
|
|
68
78
|
}
|
|
69
79
|
|
|
70
80
|
.zw-ai-component-suggestion__button:hover {
|
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
import { toRef } from 'vue';
|
|
2
1
|
import { Extension } from '@tiptap/vue-2';
|
|
3
2
|
import { createCommand } from '../utils';
|
|
4
3
|
|
|
5
4
|
export const AiComponent = Extension.create({
|
|
6
|
-
name: '
|
|
5
|
+
name: 'ai_component',
|
|
7
6
|
|
|
8
7
|
addCommands() {
|
|
9
8
|
return {
|
|
10
|
-
|
|
9
|
+
getAiSettings: createCommand(() => {
|
|
10
|
+
return this.options.aiComponent.settings;
|
|
11
|
+
}),
|
|
11
12
|
|
|
12
|
-
generateText: createCommand(async ({ commands }, { prompt, context }) => {
|
|
13
|
+
generateText: createCommand(async ({ commands }, { prompt, context, instructions }) => {
|
|
13
14
|
const aiComponent = this.options.aiComponent;
|
|
14
|
-
const result = await aiComponent.generateText({ prompt, context });
|
|
15
|
+
const result = await aiComponent.generateText({ prompt, context, instructions });
|
|
15
16
|
|
|
16
17
|
result.content = result.content.replace(/\n/g, '');
|
|
17
18
|
return result;
|