@christianriedl/utils 1.0.129 → 1.0.130
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/iOpenAI.d.ts +6 -0
- package/package.json +1 -1
- package/src/components/OpenAI.vue +79 -43
- package/src/components/OpenAIConfig.vue +209 -0
package/dist/iOpenAI.d.ts
CHANGED
|
@@ -91,3 +91,9 @@ export interface IOpenAIServiceWithVectorStore extends IOpenAIServiceWithTools {
|
|
|
91
91
|
deleteVectorStore(vectorStoreId: string): Promise<boolean>;
|
|
92
92
|
getFileId(fileUrl: string): string | undefined;
|
|
93
93
|
}
|
|
94
|
+
export interface IOpenAIAppConfig {
|
|
95
|
+
officeAITools: string;
|
|
96
|
+
openAIOptions: string;
|
|
97
|
+
openAITools: string;
|
|
98
|
+
openAISpeechModel: string;
|
|
99
|
+
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { inject, ref, toRef, computed, onUnmounted } from 'vue';
|
|
3
|
-
import { appStateSymbol, appConfigSymbol, ESize } from '@christianriedl/utils';
|
|
3
|
+
import { appStateSymbol, appConfigSymbol, ESize, EDevice } from '@christianriedl/utils';
|
|
4
4
|
import { getOpenAISymbol, IOpenAIService, IResponseResult } from '@christianriedl/utils';
|
|
5
5
|
import Microphone from '@christianriedl/utils/src/components/Microphone.vue';
|
|
6
6
|
import Camera from '@christianriedl/utils/src/components/Camera.vue';
|
|
7
|
+
import OpenAIConfig from '@christianriedl/utils/src/components/OpenAIConfig.vue';
|
|
7
8
|
|
|
8
9
|
const props = defineProps<{ prompt: string, onlymicrophone?: boolean, tools?: string[] }>();
|
|
9
|
-
const emits = defineEmits<{
|
|
10
|
+
const emits = defineEmits<{
|
|
11
|
+
(e: 'result', result: string): void,
|
|
12
|
+
(e: 'use', tools: string[], persist: boolean): void
|
|
13
|
+
}>();
|
|
10
14
|
|
|
11
15
|
const getOpenAI = inject(getOpenAISymbol)!;
|
|
12
16
|
const openAI = getOpenAI();
|
|
@@ -17,9 +21,11 @@
|
|
|
17
21
|
const qcols = computed(() => appState.size.value >= ESize.md ? 10 : 9);
|
|
18
22
|
const isCamera = ref(false);
|
|
19
23
|
const isUpload = ref(false);
|
|
24
|
+
const showConfig = ref(false);
|
|
20
25
|
const files = ref<File[]>([]);
|
|
21
26
|
const dataUrl = ref("");
|
|
22
27
|
const camera = ref<InstanceType<typeof Camera>>();
|
|
28
|
+
const htmlText = ref('');
|
|
23
29
|
|
|
24
30
|
onUnmounted(() => {
|
|
25
31
|
openAI.clearContext();
|
|
@@ -52,11 +58,28 @@
|
|
|
52
58
|
}
|
|
53
59
|
if (answer && answer.text) {
|
|
54
60
|
emits('result', answer.text);
|
|
55
|
-
|
|
61
|
+
if (openAI.options.outputFormat == 'html') {
|
|
62
|
+
|
|
63
|
+
const tempDiv = document.createElement('div');
|
|
64
|
+
tempDiv.innerHTML = answer.text;
|
|
65
|
+
const body = tempDiv.querySelector('body');
|
|
66
|
+
if (body) {
|
|
67
|
+
htmlText.value = body.innerHTML;
|
|
68
|
+
} else {
|
|
69
|
+
// Fallback: use all content if <body> is not present
|
|
70
|
+
htmlText.value = tempDiv.innerHTML;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
replyLines.value = answer.text.split('\n');
|
|
75
|
+
htmlText.value = '';
|
|
76
|
+
}
|
|
56
77
|
}
|
|
57
78
|
else {
|
|
58
|
-
|
|
59
|
-
|
|
79
|
+
htmlText.value = '';
|
|
80
|
+
const text = "KEINE KORREKTE ANTWORT : " + answer.error;
|
|
81
|
+
emits('result', text);
|
|
82
|
+
replyLines.value = [text];
|
|
60
83
|
}
|
|
61
84
|
}
|
|
62
85
|
async function onResult(result: SpeechRecognitionResult) {
|
|
@@ -88,49 +111,62 @@
|
|
|
88
111
|
reader.readAsDataURL(files.value[0]);
|
|
89
112
|
}
|
|
90
113
|
}
|
|
114
|
+
function onUse(tools: string[], persist: boolean) {
|
|
115
|
+
emits('use', tools, persist);
|
|
116
|
+
}
|
|
91
117
|
</script>
|
|
92
118
|
|
|
93
119
|
<template>
|
|
94
120
|
<microphone v-if="props.onlymicrophone" lang="de-AT" @result="onResult"></microphone>
|
|
95
121
|
<v-container v-else fluid class="bg-office">
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
122
|
+
<v-row dense align="center" class="font-weight-bold">
|
|
123
|
+
<v-col :cols="qcols">
|
|
124
|
+
<v-text-field name="subject" type="text" v-model="question" density="compact" hide-details></v-text-field>
|
|
125
|
+
</v-col>
|
|
126
|
+
<v-col :cols="12 - qcols">
|
|
127
|
+
<v-btn class="aibutton" @click="onComplete">AI</v-btn>
|
|
128
|
+
<microphone lang="de-AT" cls="aibutton" @result="onResult"></microphone>
|
|
129
|
+
<v-btn variant="tonal" class="bg-office aibutton" @click="onCamera">
|
|
130
|
+
<v-icon size="large" icon="$webcam" color="primary"></v-icon>
|
|
131
|
+
</v-btn>
|
|
132
|
+
<v-btn variant="tonal" class="bg-office aibutton" @click="isUpload=true;replyLines=[]">
|
|
133
|
+
<v-icon size="large" icon="$image" color="primary"></v-icon>
|
|
134
|
+
</v-btn>
|
|
135
|
+
<v-btn variant="tonal" class="bg-office aibutton" @click="showConfig=true">
|
|
136
|
+
<v-icon size="large" icon="$settings" color="primary"></v-icon>
|
|
137
|
+
</v-btn>
|
|
138
|
+
</v-col>
|
|
139
|
+
</v-row>
|
|
140
|
+
<v-row v-if="isCamera" dense align="center">
|
|
141
|
+
<v-col cols="6">
|
|
142
|
+
<camera :resolution="{ width: 640, height: 480 }" ref="camera" autoplay></camera>
|
|
143
|
+
</v-col>
|
|
144
|
+
<v-col cols="6">
|
|
145
|
+
<v-row v-for="(line, index) in replyLines" :key=index dense align="center">
|
|
146
|
+
<v-col cols="12">{{line}}</v-col>
|
|
147
|
+
</v-row>
|
|
148
|
+
</v-col>
|
|
149
|
+
</v-row>
|
|
150
|
+
<v-row v-if="isUpload" dense align="center">
|
|
151
|
+
<v-col cols="6">
|
|
152
|
+
<v-file-input show-size clearable multiple hide-details v-model="files" accept="image/*" label="Select File" prepend-icon="" append-icon="" @update:modelValue="onFile">
|
|
153
|
+
</v-file-input>
|
|
154
|
+
</v-col>
|
|
155
|
+
<v-col cols="6">
|
|
156
|
+
<img width="100%" :src="dataUrl">
|
|
157
|
+
</v-col>
|
|
158
|
+
</v-row>
|
|
159
|
+
<v-row v-if="htmlText" dense >
|
|
160
|
+
<v-col cols="12">
|
|
161
|
+
<div v-html="htmlText"></div>
|
|
162
|
+
</v-col>
|
|
163
|
+
</v-row>
|
|
164
|
+
<v-row v-else v-for="(line, index) in replyLines" :key=index dense align="center">
|
|
165
|
+
<v-col cols="12">{{line}}</v-col>
|
|
166
|
+
</v-row>
|
|
167
|
+
<v-dialog v-model="showConfig">
|
|
168
|
+
<OpenAIConfig :tools="props.tools" @use="onUse" @back="showConfig=false"></OpenAIConfig>
|
|
169
|
+
</v-dialog>
|
|
134
170
|
</v-container>
|
|
135
171
|
</template>
|
|
136
172
|
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { inject, ref, reactive } from 'vue';
|
|
3
|
+
import { appConfigSymbol, AppConfig, Dictionary } from '@christianriedl/utils';
|
|
4
|
+
import { getOpenAISymbol, IOpenAIServiceWithVectorStore, IOpenAIAppConfig, IOpenAIOptions, ITool } from '@christianriedl/utils';
|
|
5
|
+
|
|
6
|
+
const props = defineProps<{ tools?: string[] }>();
|
|
7
|
+
const emits = defineEmits<{
|
|
8
|
+
(e: 'use', tools: string[], persist: boolean): void,
|
|
9
|
+
(e: 'back'): void
|
|
10
|
+
}>();
|
|
11
|
+
|
|
12
|
+
const getOpenAI = inject(getOpenAISymbol)!;
|
|
13
|
+
const openAI = getOpenAI() as IOpenAIServiceWithVectorStore;
|
|
14
|
+
const appConfig = inject(appConfigSymbol)! as AppConfig;
|
|
15
|
+
const aiAppConfig = appConfig as unknown as IOpenAIAppConfig;
|
|
16
|
+
const options = reactive<IOpenAIOptions>(Object.assign({}, openAI.options));
|
|
17
|
+
const tools = reactive<Dictionary<any>>(Object.assign({}, openAI.aiTools));
|
|
18
|
+
const use = reactive<Dictionary<boolean>>({});
|
|
19
|
+
const optionsChanged = ref(false);
|
|
20
|
+
const toolsChanged = ref(false);
|
|
21
|
+
const useChanged = ref(false);
|
|
22
|
+
const outputFormats = ['text', 'markdown', 'html'];
|
|
23
|
+
const reasoningEffortLevels = ['minimum', 'low', 'medium', 'high', null];
|
|
24
|
+
const mcpApprovals = ['always', 'never', null];
|
|
25
|
+
const vectorStoreNames: string[] = [];
|
|
26
|
+
const fileSearch = reactive<Dictionary<string[]>>({});
|
|
27
|
+
|
|
28
|
+
initializeTools();
|
|
29
|
+
|
|
30
|
+
function initializeTools() {
|
|
31
|
+
for (const key in openAI.vectorStore) {
|
|
32
|
+
vectorStoreNames.push(key);
|
|
33
|
+
}
|
|
34
|
+
for (const key in tools) {
|
|
35
|
+
const tool = tools[key];
|
|
36
|
+
use[key] = false;
|
|
37
|
+
if (props.tools && props.tools.includes(key)) {
|
|
38
|
+
use[key] = true;
|
|
39
|
+
}
|
|
40
|
+
if (tool.type === 'file_search') {
|
|
41
|
+
const names = [];
|
|
42
|
+
for (let i = 0; i < tool.vector_store_ids.length; i++) {
|
|
43
|
+
const vs = openAI.getVectorStore(tool.vector_store_ids[i]);
|
|
44
|
+
if (vs && props.tools && props.tools.includes(vs.name)) {
|
|
45
|
+
names.push(vs.name);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
fileSearch[key] = names;
|
|
49
|
+
}
|
|
50
|
+
if (tool.type.startsWith("web_search")) {
|
|
51
|
+
tool.user_location = Object.assign({}, tool.user_location);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function onOptionChange() {
|
|
56
|
+
optionsChanged.value = true;
|
|
57
|
+
}
|
|
58
|
+
function onToolChange() {
|
|
59
|
+
toolsChanged.value = true;
|
|
60
|
+
}
|
|
61
|
+
function onUseChange() {
|
|
62
|
+
useChanged.value = true;
|
|
63
|
+
}
|
|
64
|
+
function onSave() {
|
|
65
|
+
const persist = window.confirm('Do you want to persist the changes?');
|
|
66
|
+
if (toolsChanged.value) {
|
|
67
|
+
for (const key in fileSearch) {
|
|
68
|
+
const vsNames = fileSearch[key];
|
|
69
|
+
for (let i = 0; i < vsNames.length; i++) {
|
|
70
|
+
tools[key].vector_store_ids[i] = openAI.vectorStore![vsNames[i]].id;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
openAI.aiTools = tools as Dictionary<ITool>;
|
|
74
|
+
if (persist) {
|
|
75
|
+
aiAppConfig.openAITools = JSON.stringify(openAI.aiTools);
|
|
76
|
+
appConfig.saveItem(appConfig.items['openAITools']);
|
|
77
|
+
}
|
|
78
|
+
toolsChanged.value = false;
|
|
79
|
+
}
|
|
80
|
+
if (optionsChanged.value) {
|
|
81
|
+
openAI.options = options;
|
|
82
|
+
if (persist) {
|
|
83
|
+
aiAppConfig.openAIOptions = JSON.stringify(options);
|
|
84
|
+
appConfig.saveItem(appConfig.items['openAIOptions']);
|
|
85
|
+
}
|
|
86
|
+
optionsChanged.value = false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function onSaveUsage() {
|
|
90
|
+
const persist = window.confirm('Do you want to persist the changes?');
|
|
91
|
+
const useTools: string[] = [];
|
|
92
|
+
for (const key in use) {
|
|
93
|
+
if (use[key]) {
|
|
94
|
+
useTools.push(key);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
emits('use', useTools, persist);
|
|
98
|
+
useChanged.value = false;
|
|
99
|
+
}
|
|
100
|
+
</script>
|
|
101
|
+
|
|
102
|
+
<template>
|
|
103
|
+
<v-container fluid class="bg-office">
|
|
104
|
+
<h2>OpenAI Settings</h2>
|
|
105
|
+
<v-row dense align="center">
|
|
106
|
+
<v-col cols="3">
|
|
107
|
+
<v-text-field v-model="options.model" type="string" label="Model" @change="onOptionChange"></v-text-field>
|
|
108
|
+
</v-col>
|
|
109
|
+
<v-col cols="2">
|
|
110
|
+
<v-select v-model="options.outputFormat" :items="outputFormats" persistent-hint
|
|
111
|
+
label="Format" @update:modelValue="onOptionChange" solo single-line>
|
|
112
|
+
</v-select>
|
|
113
|
+
</v-col>
|
|
114
|
+
<v-col cols="2">
|
|
115
|
+
<v-select v-model="options.reasoning_effort_level" :items="reasoningEffortLevels" persistent-hint
|
|
116
|
+
label="Reasoning" @update:modelValue="onOptionChange" solo single-line>
|
|
117
|
+
</v-select>
|
|
118
|
+
</v-col>
|
|
119
|
+
<v-col cols="2">
|
|
120
|
+
<v-text-field v-model="options.max_output_tokens" type="number" label="Max Token" @change="onOptionChange"></v-text-field>
|
|
121
|
+
</v-col>
|
|
122
|
+
<v-col cols="1">
|
|
123
|
+
<v-text-field v-model="options.temperature" type="number" label="Temperature" @change="onOptionChange"></v-text-field>
|
|
124
|
+
</v-col>
|
|
125
|
+
<v-col cols="1">
|
|
126
|
+
<v-text-field v-model="options.top_p" type="number" label="Top P" @change="onOptionChange"></v-text-field>
|
|
127
|
+
</v-col>
|
|
128
|
+
<v-col cols="1">
|
|
129
|
+
<v-checkbox v-model="options.store" label="Store" @change="onOptionChange"></v-checkbox>
|
|
130
|
+
</v-col>
|
|
131
|
+
</v-row>
|
|
132
|
+
<v-row dense align="center">
|
|
133
|
+
<v-col cols="12">
|
|
134
|
+
<v-text-field v-model="options.instructions" type="string" label="System Message" @change="onOptionChange"></v-text-field>
|
|
135
|
+
</v-col>
|
|
136
|
+
</v-row>
|
|
137
|
+
<template v-for="(tool, key) in tools">
|
|
138
|
+
<v-row v-if="tool.type == 'mcp'" dense align="center">
|
|
139
|
+
<v-col cols="2">
|
|
140
|
+
<v-text-field v-model="tool.server_label" disabled type="string" label="mcp" @change="onToolChange"></v-text-field>
|
|
141
|
+
</v-col>
|
|
142
|
+
<v-col cols="4">
|
|
143
|
+
<v-text-field v-model="tool.server_url" type="string" label="Url" @change="onToolChange"></v-text-field>
|
|
144
|
+
</v-col>
|
|
145
|
+
<v-col cols="3">
|
|
146
|
+
<v-text-field v-model="tool.server_description" type="string" label="Description" @change="onToolChange"></v-text-field>
|
|
147
|
+
</v-col>
|
|
148
|
+
<v-col cols="2">
|
|
149
|
+
<v-select v-model="tool.require_approval" :items="mcpApprovals" persistent-hint
|
|
150
|
+
label="Approval" @update:modelValue="onToolChange" solo single-line>
|
|
151
|
+
</v-select>
|
|
152
|
+
</v-col>
|
|
153
|
+
<v-col cols="1">
|
|
154
|
+
<v-checkbox v-model="use[key]" label="Use" @update:modelValue="onUseChange"></v-checkbox>
|
|
155
|
+
</v-col>
|
|
156
|
+
</v-row>
|
|
157
|
+
<v-row v-if="tool.type == 'file_search'" dense align="center">
|
|
158
|
+
<v-col cols="2">
|
|
159
|
+
<v-text-field :model-value="key" disabled type="string" label="file_search" @change="onToolChange"></v-text-field>
|
|
160
|
+
</v-col>
|
|
161
|
+
<v-col cols="7">
|
|
162
|
+
<v-select v-model="fileSearch[key]" :items="vectorStoreNames" multiple persistent-hint
|
|
163
|
+
label="Vector Stores" @update:modelValue="onToolChange" solo single-line>
|
|
164
|
+
</v-select>
|
|
165
|
+
</v-col>
|
|
166
|
+
<v-col cols="2">
|
|
167
|
+
<v-text-field v-model="tool.max_num_results" type="string" label="Max Results" @change="onToolChange"></v-text-field>
|
|
168
|
+
</v-col>
|
|
169
|
+
<v-col cols="1">
|
|
170
|
+
<v-checkbox v-model="use[key]" label="Use" @update:modelValue="onUseChange"></v-checkbox>
|
|
171
|
+
</v-col>
|
|
172
|
+
</v-row>
|
|
173
|
+
<v-row v-if="tool.type.startsWith('web_search')" dense align="center">
|
|
174
|
+
<v-col cols="2">
|
|
175
|
+
<v-text-field :model-value="key" disabled type="string" label="file_search" @change="onToolChange"></v-text-field>
|
|
176
|
+
</v-col>
|
|
177
|
+
<v-col cols="2">
|
|
178
|
+
<v-text-field v-model="tool.user_location.country" type="string" label="Country" @change="onToolChange"></v-text-field>
|
|
179
|
+
</v-col>
|
|
180
|
+
<v-col cols="3">
|
|
181
|
+
<v-text-field v-model="tool.user_location.city" type="string" label="City" @change="onToolChange"></v-text-field>
|
|
182
|
+
</v-col>
|
|
183
|
+
<v-col cols="4">
|
|
184
|
+
<v-text-field v-model="tool.user_location.region" type="string" label="Region" @change="onToolChange"></v-text-field>
|
|
185
|
+
</v-col>
|
|
186
|
+
<v-col cols="1">
|
|
187
|
+
<v-checkbox v-model="use[key]" label="Use" @update:modelValue="onUseChange"></v-checkbox>
|
|
188
|
+
</v-col>
|
|
189
|
+
</v-row>
|
|
190
|
+
</template>
|
|
191
|
+
<v-row dense align="center">
|
|
192
|
+
<v-col cols="2">
|
|
193
|
+
<v-btn class="bg-office" @click="emits('back')">BACK
|
|
194
|
+
<v-icon icon="$back"></v-icon>
|
|
195
|
+
</v-btn>
|
|
196
|
+
</v-col>
|
|
197
|
+
<v-col cols="2">
|
|
198
|
+
<v-btn :disabled="!optionsChanged && !toolsChanged" class="bg-office" @click="onSave">SAVE
|
|
199
|
+
<v-icon icon="$save"></v-icon>
|
|
200
|
+
</v-btn>
|
|
201
|
+
</v-col>
|
|
202
|
+
<v-col cols="2">
|
|
203
|
+
<v-btn :disabled="!useChanged" class="bg-office" @click="onSaveUsage">SAVE TOOL USE
|
|
204
|
+
<v-icon icon="$save"></v-icon>
|
|
205
|
+
</v-btn>
|
|
206
|
+
</v-col>
|
|
207
|
+
</v-row>
|
|
208
|
+
</v-container>
|
|
209
|
+
</template>
|