@nebulars/sseengine 1.0.0 → 1.3.44

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 (40) hide show
  1. package/.prettierignore +0 -0
  2. package/.prettierrc.cjs +8 -0
  3. package/dist/sseengine.css +1 -0
  4. package/dist/sseengine.umd.js +1 -0
  5. package/package.json +19 -4
  6. package/src/components/{fqa-answer → sse-answer}/index.vue +7 -6
  7. package/src/components/{fqa-chat → sse-chat}/index.vue +9 -9
  8. package/src/components/sse-drawer/index.vue +256 -0
  9. package/src/components/sse-drawer/modify.less +38 -0
  10. package/src/components/sse-enter/index.vue +210 -0
  11. package/src/components/sse-enter/modify.less +101 -0
  12. package/src/components/{fqa-hub → sse-hub}/index.vue +4 -4
  13. package/src/components/sse-menus/index.vue +288 -0
  14. package/src/components/sse-menus/popover-modify.less +26 -0
  15. package/src/components/sse-notes/index.vue +236 -0
  16. package/src/components/sse-notes/modify.less +3 -0
  17. package/src/components/{fqa-query → sse-query}/index.vue +12 -12
  18. package/src/components/{fqa-recursion → sse-recursion}/index.vue +4 -4
  19. package/src/components/{fqa-root → sse-root}/index.vue +1 -1
  20. package/src/components/sse-submit/index.vue +63 -0
  21. package/src/components/sse-submit/modify.less +8 -0
  22. package/src/index.js +11 -2
  23. package/src/index.vue +2 -2
  24. package/src/store/feedback.js +8 -0
  25. package/src/store/index.js +166 -3
  26. package/src/store/info.js +20 -0
  27. package/src/store/launch.js +18 -0
  28. package/src/store/menus.js +45 -0
  29. package/src/store/modify.less +127 -0
  30. package/src/store/presets.js +95 -0
  31. package/src/store/remote.js +34 -0
  32. package/src/store/session.js +57 -0
  33. package/src/store/sevani.js +15 -0
  34. package/src/store/sse.js +223 -0
  35. package/src/store/sup.js +7 -0
  36. package/sseengine.zip +0 -0
  37. package/src/components/fqa-icon/index.vue +0 -36
  38. /package/src/components/{fqa-chat → sse-chat}/conversation.less +0 -0
  39. /package/src/components/{fqa-chat → sse-chat}/sup.less +0 -0
  40. /package/src/components/{fqa-flip → sse-flip}/index.vue +0 -0
@@ -0,0 +1,236 @@
1
+ <style lang="less" scoped>
2
+ .fqa-notes {
3
+ &,
4
+ &-popup,
5
+ &-panel {
6
+ height: 36vh;
7
+ }
8
+
9
+ & {
10
+ position: fixed;
11
+ left: 0;
12
+ right: 0;
13
+ bottom: 0;
14
+ z-index: 1;
15
+
16
+ box-shadow: 0 0 @gap rgba(0, 0, 0, 0.1);
17
+ color: @color-text;
18
+ background-color: @color-assist;
19
+ }
20
+
21
+ &-panel {
22
+ &-title,
23
+ &-content,
24
+ &-excerpt {
25
+ padding: @gap;
26
+ }
27
+
28
+ &-content {
29
+ flex: 1;
30
+ }
31
+
32
+ &-title {
33
+ gap: @unit;
34
+ border-bottom: 1px solid @color-line;
35
+ }
36
+
37
+ &-excerpt {
38
+ border-top: 1px solid @color-line;
39
+ }
40
+
41
+ &-number {
42
+ display: block;
43
+ opacity: 0.75;
44
+ width: @sina;
45
+ height: @sina;
46
+ text-align: center;
47
+ border-radius: @space;
48
+ border: 1px solid @color-text;
49
+ }
50
+ }
51
+
52
+ &-arrow-prev,
53
+ &-arrow-next {
54
+ & {
55
+ width: @space;
56
+ height: @space;
57
+ color: darken(@color-text, 20%);
58
+ transition: all @fast;
59
+ opacity: 0;
60
+ z-index: 1;
61
+ }
62
+
63
+ &:hover {
64
+ color: @color-text;
65
+ }
66
+ }
67
+
68
+ &-arrow-prev {
69
+ left: -@space;
70
+ }
71
+
72
+ &-arrow-next {
73
+ right: -@space;
74
+ }
75
+
76
+ &:hover {
77
+ .fqa-notes-arrow-prev {
78
+ left: @space;
79
+ opacity: 1;
80
+ }
81
+
82
+ .fqa-notes-arrow-next {
83
+ right: @space;
84
+ opacity: 1;
85
+ }
86
+ }
87
+ }
88
+ </style>
89
+
90
+ <template>
91
+ sliders: {{ sliders }}
92
+ <a-drawer class="fqa-notes-popup" width="100%" placement="bottom" v-model:open="sseengine.notes[id]" @after-open-change="onOpen" @close="onClose" :close-icon="false" :footer="false" :header-style="{ display: 'none' }" :mask-style="{ background: 'rgba(0, 0, 0, 0.1)' }" height="36vh">
93
+ 11111
94
+ <a-carousel class="fqa-notes" arrows :dots="false" :autoplay="false" :after-change="onChange" ref="notes">
95
+ <template #prevArrow>
96
+ <div class="fqa-notes-arrow-prev">
97
+ <fqa-icon icon="left-outlined" color="#cdcecf" :size="20" />
98
+ </div>
99
+ </template>
100
+ <template #nextArrow>
101
+ <div class="fqa-notes-arrow-next">
102
+ <fqa-icon icon="right-outlined" :size="20" />
103
+ </div>
104
+ </template>
105
+
106
+ <section v-for="({ id, title, content, file_url, source } = {}, index) in sliders" :key="index">
107
+ <a-flex vertical class="fqa-notes-panel">
108
+ <a-flex class="fqa-notes-panel-title" justify="space-between">
109
+ <slot name="title" :title="title" />
110
+ <strong class="fqa-notes-panel-number">{{ id }}</strong>
111
+ </a-flex>
112
+ <div class="fqa-notes-panel-content scroll" v-if="content">
113
+ <slot name="content" :content="content" v-if="index === current" />
114
+ </div>
115
+ <div class="fqa-notes-panel-excerpt" v-if="file_url || source">
116
+ <slot name="excerpt" :url="file_url" :source="source" />
117
+ </div>
118
+ </a-flex>
119
+ </section>
120
+ </a-carousel>
121
+ </a-drawer>
122
+ </template>
123
+
124
+ <script>
125
+ export default {
126
+ props: {
127
+ id: {
128
+ type: [String],
129
+ },
130
+ references: {
131
+ type: [Array],
132
+ required: true,
133
+ default: [],
134
+ },
135
+ },
136
+
137
+ data() {
138
+ return {
139
+ current: 0,
140
+ active: 0,
141
+ sliders: [],
142
+ container: null,
143
+ sups: [],
144
+ visible: false,
145
+ };
146
+ },
147
+
148
+ watch: {
149
+ 'sseengine.sup': {
150
+ handler(sup) {
151
+ if (!sup) {
152
+ this.container = null;
153
+ this.sups = null;
154
+ this.active = 0;
155
+ this.sliders = [];
156
+ return;
157
+ }
158
+
159
+ this.container = this.$util.closest(sup, '.fqa-answer', false);
160
+ this.sups = this.getSups();
161
+ this.active = this.getSupByActive();
162
+ this.sliders = this.getSliders(this.references);
163
+ },
164
+
165
+ deep: true,
166
+ immediate: true,
167
+ },
168
+ },
169
+
170
+ methods: {
171
+ getSups() {
172
+ return Array.from(this.container.querySelectorAll('sup'));
173
+ },
174
+
175
+ getSupByActive(active = 'active') {
176
+ return this.sups.findIndex(({ classList }) => classList.contains(active));
177
+ },
178
+
179
+ getSupByIndex(index) {
180
+ return this.sups[index];
181
+ },
182
+
183
+ getSupAttr(sup) {
184
+ const { id, dataset } = sup;
185
+ const { key } = dataset;
186
+
187
+ return { id, key };
188
+ },
189
+
190
+ getSliders(references) {
191
+ return this.sups.map(sup => {
192
+ const { key } = this.getSupAttr(sup);
193
+ return references.find(({ id }) => id == key);
194
+ });
195
+ },
196
+
197
+ getSup(id) {
198
+ return document.querySelector(`[id='${this.note}'][data-key='${id}']`);
199
+ },
200
+
201
+ moveActive(index, no = false) {
202
+ this.$refs.notes.goTo(index, no);
203
+ },
204
+
205
+ onOpen() {
206
+ this.moveActive(this.active, true);
207
+ this.visible = true;
208
+ },
209
+
210
+ onClose() {
211
+ this.$util.sup.clean();
212
+ this.visible = false;
213
+ },
214
+
215
+ onChange(index) {
216
+ // No Visible
217
+ if (!this.visible) {
218
+ return;
219
+ }
220
+
221
+ // Current
222
+ this.current = index;
223
+
224
+ // Get Sup
225
+ const sup = this.getSupByIndex(index);
226
+
227
+ if (sup) {
228
+ // Active
229
+ this.$util.sup.active(sup);
230
+ // Scrolling
231
+ this.$util.sup.scrolling(sup);
232
+ }
233
+ },
234
+ },
235
+ };
236
+ </script>
@@ -0,0 +1,3 @@
1
+ .fqa-notes-popup {
2
+ background-color: @color-assist !important;
3
+ }
@@ -79,7 +79,7 @@
79
79
  <template>
80
80
  <section class="fqa-query fqa-conversation" :query-id="item.id" :x-trace-id="item.x_trace_id" @mouseenter.stop="enterHandler" @mouseleave.stop="leaveHandler">
81
81
  <!-- Editable -->
82
- <div class="fqa-query-editable" v-if="editable && !chat.produce">
82
+ <div class="fqa-query-editable" v-if="editable && !sseengine.produce">
83
83
  <a-textarea class="fqa-query-input" :auto-size="{ minRows: 3, maxRows: 10 }" v-model:value="origin" @press-enter="e => enterHandler(e, item)" />
84
84
  <a-space class="fqa-query-bunny">
85
85
  <fqa-button class="fqa-query-cancel" :width="80" :instance="20" :radius="40" hover="#e7e8e9" @click="e => cancelHandler(e, item)">{{ $t('global.cancel') }}</fqa-button>
@@ -91,7 +91,7 @@
91
91
  <div v-else class="fqa-query-content">{{ item.content }}</div>
92
92
 
93
93
  <!-- Control -->
94
- <div class="fqa-query-control" :class="{ hover: app.mobile }" v-if="!editable && !chat.produce">
94
+ <div class="fqa-query-control" :class="{ hover: app.mobile }" v-if="!editable && !sseengine.produce">
95
95
  <!-- Copy -->
96
96
  <fqa-button :size="30" hover="#e7e8e9" v-clipboard:copy="item.content" v-clipboard:success="copySuccess">
97
97
  <fqa-icon :icon="copied ? 'CheckOutlined' : 'CopyOutlined'" />
@@ -104,10 +104,10 @@
104
104
  </div>
105
105
 
106
106
  <!-- Worry -->
107
- <!-- <fqa-worry :source="$util.anxious(item)" :type="item.type" :produce="chat.produce" /> -->
107
+ <!-- <fqa-worry :source="$util.anxious(item)" :type="item.type" :produce="sseengine.produce" /> -->
108
108
 
109
109
  <!-- Next -->
110
- <fqa-recursion v-if="item.children.length" :source="item.children" :active="active" />
110
+ <sse-recursion v-if="item.children.length" :source="item.children" :active="active" />
111
111
  </section>
112
112
  </template>
113
113
 
@@ -152,16 +152,16 @@ export default {
152
152
 
153
153
  async submitHandler(e, item) {
154
154
  // Start SSE
155
- await this.$store.dispatch('chat/SSE_CONNECT', { session_id: this.app.id, content: this.origin, parent: item.parent });
155
+ await this.$store.dispatch('sseengine/SSE_CONNECT', { session_id: this.app.id, content: this.origin, parent: item.parent });
156
156
 
157
157
  // Clean Input
158
158
  this.cancelHandler();
159
159
 
160
160
  // Clean Stream
161
- await this.$store.dispatch('chat/STREAM_CACHE', null);
161
+ await this.$store.dispatch('sseengine/STREAM_CACHE', null);
162
162
 
163
163
  // Get Parent Active
164
- const { children } = this.$util.deepfind(this.chat.info.history, item.parent);
164
+ const { children } = this.$util.deepfind(this.sseengine.info.history, item.parent);
165
165
 
166
166
  // Update Active
167
167
  this.updateActive(children.length + 1);
@@ -183,16 +183,16 @@ export default {
183
183
  }
184
184
 
185
185
  // Set Current Element
186
- await this.$store.dispatch('chat/SEVANI_UPDATE', event);
186
+ await this.$store.dispatch('sseengine/SEVANI_UPDATE', event);
187
187
 
188
188
  // Clean Controls First
189
- await this.$store.dispatch('chat/SEVANI_CLEAN', 'fqa-query-control');
189
+ await this.$store.dispatch('sseengine/SEVANI_CLEAN', 'fqa-query-control');
190
190
 
191
191
  // Sleep 1
192
192
  await this.$util.kitchen.sleep(1);
193
193
 
194
194
  // Get Control
195
- const control = await this.$store.dispatch('chat/SEVANI_FIND', 'fqa-query-control');
195
+ const control = await this.$store.dispatch('sseengine/SEVANI_FIND', 'fqa-query-control');
196
196
 
197
197
  // Add Class into List
198
198
  control?.classList?.add('hover');
@@ -205,10 +205,10 @@ export default {
205
205
  }
206
206
 
207
207
  // Remove Element
208
- this.$store.dispatch('chat/SEVANI_UPDATE', { sevani: null });
208
+ this.$store.dispatch('sseengine/SEVANI_UPDATE', { sevani: null });
209
209
 
210
210
  // Remove Class from Controls
211
- await this.$store.dispatch('chat/SEVANI_CLEAN', 'fqa-query-control');
211
+ await this.$store.dispatch('sseengine/SEVANI_CLEAN', 'fqa-query-control');
212
212
  },
213
213
  },
214
214
  };
@@ -11,15 +11,15 @@
11
11
  <div class="fqa-recursion">
12
12
  <!-- Flip -->
13
13
  <template v-if="flip">
14
- <fqa-flip :freeze="chat.produce" :source="source" :active="current" @update:active="updateActive">
14
+ <sse-flip :freeze="sseengine.produce" :source="source" :active="current" @update:active="updateActive">
15
15
  <template #item="{ item, active }">
16
- <fqa-hub :item="item" :active="active" @update:active="updateActive" />
16
+ <sse-hub :item="item" :active="active" @update:active="updateActive" />
17
17
  </template>
18
- </fqa-flip>
18
+ </sse-flip>
19
19
  </template>
20
20
 
21
21
  <!-- No Flip -->
22
- <fqa-hub v-else :item="first" />
22
+ <sse-hub v-else :item="first" />
23
23
  </div>
24
24
  </template>
25
25
 
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <section class="fqa-root fqa-conversation">
3
3
  <!-- Next -->
4
- <fqa-recursion v-if="item.children.length" :source="item.children" :active="active" />
4
+ <sse-recursion v-if="item.children.length" :source="item.children" :active="active" />
5
5
  </section>
6
6
  </template>
7
7
 
@@ -0,0 +1,63 @@
1
+ <style lang="less">
2
+ @keyframes up {
3
+ 0% {
4
+ transform: translateY(0);
5
+ opacity: 1;
6
+ }
7
+ 49% {
8
+ transform: translateY(-@slit);
9
+ opacity: 0;
10
+ }
11
+ 50% {
12
+ transform: translateY(@slit);
13
+ opacity: 0;
14
+ }
15
+ 100% {
16
+ transform: translateY(0);
17
+ opacity: 1;
18
+ }
19
+ }
20
+
21
+ .fqa-submit {
22
+ width: @size-submit;
23
+ height: @size-submit;
24
+ border: 1px solid darken(@color-submit, 4%);
25
+ box-shadow: 0 @unit @dozen rgba(0, 0, 0, 0.16);
26
+ display: flex;
27
+ justify-content: center;
28
+ align-items: center;
29
+ border-radius: 100%;
30
+ cursor: pointer;
31
+ transition: all @effect;
32
+ background: linear-gradient(to top, darken(@color-submit, 4%) 0%, @color-submit 100%);
33
+
34
+ &:not(.block) {
35
+ &:hover {
36
+ color: @color-primary !important;
37
+
38
+ .fqa-icon {
39
+ animation: up 1.24s linear infinite;
40
+ }
41
+ }
42
+ }
43
+ }
44
+ </style>
45
+
46
+ <template>
47
+ <div class="fqa-submit" :class="{ block: sseengine.produce }" v-if="sseengine.produce" @click="onProduceStop">
48
+ <fqa-icon icon="BorderOutlined" cursor="pointer" color="#484848" :size="20" />
49
+ </div>
50
+ <div class="fqa-submit" v-else>
51
+ <fqa-icon icon="ArrowUpOutlined" cursor="pointer" :size="20" />
52
+ </div>
53
+ </template>
54
+
55
+ <script>
56
+ export default {
57
+ methods: {
58
+ async onProduceStop() {
59
+ this.$store.dispatch('sseengine/SSE_STOP', this.app.id);
60
+ },
61
+ },
62
+ };
63
+ </script>
@@ -0,0 +1,8 @@
1
+ .fqa-cancel,
2
+ .fqa-submit {
3
+ &.block {
4
+ .fqa-icon {
5
+ background-color: rgba(0, 0, 0, 0.88);
6
+ }
7
+ }
8
+ }
package/src/index.js CHANGED
@@ -31,8 +31,17 @@ export default register((app, { name, $store, $router, module }) => {
31
31
  app.component(name, entry);
32
32
 
33
33
  // Register Components
34
- componentRegister(app);
34
+ componentRegister(
35
+ // App
36
+ app,
37
+
38
+ // Components Modules
39
+ import.meta.glob('./components/**/*.vue', { eager: true }),
40
+ );
35
41
 
36
42
  // Register Less
37
- lessRegister();
43
+ lessRegister(
44
+ // Css Modules
45
+ import.meta.glob('./components/**/*.less', { eager: true }),
46
+ );
38
47
  });
package/src/index.vue CHANGED
@@ -1,9 +1,9 @@
1
1
  <template>
2
- <fqa-chat />
2
+ <sse-chat />
3
3
  </template>
4
4
 
5
5
  <script>
6
6
  export default {
7
- name: 'sse'
7
+ name: 'sseengine',
8
8
  };
9
9
  </script>
@@ -0,0 +1,8 @@
1
+ export default ({ http }) => {
2
+ return {
3
+ async FEEDBACK({}, { feedback, id: answer_id }) {
4
+ const { data } = await http.api.sseUpdateFeedback({ feedback, answer_id });
5
+ return data;
6
+ },
7
+ };
8
+ };
@@ -1,15 +1,178 @@
1
+ // Use Remove
2
+ import { default as REMOTE } from './remote';
3
+
4
+ // Use Assist
5
+ import { default as MENUS } from './menus';
6
+
7
+ // Use Sevani
8
+ import { default as SEVANI } from './sevani';
9
+
10
+ // Use Feedback
11
+ import { default as FEEDBACK } from './feedback';
12
+
13
+ // Use Info
14
+ import { default as INFO } from './info';
15
+
16
+ // Use Sup
17
+ import { default as SUP } from './sup';
18
+
19
+ // Use Session
20
+ import { default as SESSION } from './session';
21
+
22
+ // Use SSE
23
+ import { default as SSE } from './sse';
24
+
25
+ // Use Launch
26
+ import { default as LAUNCH } from './launch';
27
+
1
28
  // As Store
2
29
  export default (proxy = {}) => {
3
- const state = {};
30
+ const state = {
31
+ // Status Code (From AI)
32
+ status: {
33
+ // Default
34
+ 0: '智能生成中',
35
+
36
+ // Simple Think
37
+ 11000: '检索中',
38
+ 11001: '检索完成',
39
+ 14003: '检索失败',
40
+ 12002: '开始回复',
41
+ 12000: '回复完成',
42
+ 15000: '模型似乎遇到了问题,请重试~', // '回复失败/错误/无回复',
43
+
44
+ // Deep Think
45
+ 21000: '思考中',
46
+ 21001: '思考完成',
47
+ 24003: '思考失败',
48
+ 22002: '开始回复',
49
+ 22000: '回复完成',
50
+ 25000: '模型似乎遇到了问题,请重试~', // '回复失败/错误/无回复',
51
+ },
52
+
53
+ // Code for Status
54
+ code: 0,
55
+
56
+ // List - Assists
57
+ list: [],
58
+
59
+ // Menus Params
60
+ menus: {
61
+ page: 0,
62
+ size: 20,
63
+ more: true,
64
+ },
65
+
66
+ // Info - Big Data
67
+ info: {
68
+ current: null,
69
+ title: '',
70
+ history: [],
71
+ },
72
+
73
+ // Launch
74
+ launch: {
75
+ url: '',
76
+ mode: 'web',
77
+ query: {},
78
+ },
79
+
80
+ // Attach - Map Info
81
+ attach: {},
82
+
83
+ // Aovery - Map Info
84
+ aovery: {},
85
+
86
+ // Anchor
87
+ anchor: -1,
88
+
89
+ // Explicit
90
+ explicit: [],
91
+
92
+ // Deep RAG
93
+ rag: 'false',
94
+
95
+ // Prompt
96
+ prompt: '',
97
+
98
+ // Stream - SSE
99
+ stream: null,
100
+
101
+ // Tower - SSE Instance
102
+ tower: null,
103
+
104
+ // Options
105
+ options: {},
106
+
107
+ // Presets
108
+ presets: [],
109
+
110
+ // Focusing
111
+ focus: false,
112
+
113
+ // Opening
114
+ opener: ['day.1', 'day.2', 'day.7', 'day.30', 'day.more'],
115
+
116
+ // Focal
117
+ focal: null,
118
+
119
+ // Notes
120
+ notes: {},
121
+
122
+ // Selecting
123
+ select: [],
124
+
125
+ // Produce - SSE
126
+ produce: false,
127
+
128
+ // Sup - Active
129
+ sup: null,
130
+
131
+ // Special Hover
132
+ sevani: null,
133
+ };
4
134
 
5
135
  // Reset
6
136
  const reset = proxy.json.clone(state);
7
137
 
8
138
  const actions = {
9
139
  async CLEAN() {
10
- const {} = reset;
11
- return {};
140
+ const { info, attach, prompt, select, produce } = reset;
141
+ return {
142
+ info,
143
+ attach,
144
+ prompt,
145
+ select,
146
+ produce,
147
+ };
12
148
  },
149
+
150
+ // Remote Control
151
+ ...REMOTE(proxy),
152
+
153
+ // Menus
154
+ ...MENUS(proxy),
155
+
156
+ // Sevani
157
+ ...SEVANI(proxy),
158
+
159
+ // Feedback
160
+ ...FEEDBACK(proxy),
161
+
162
+ // Chat Info
163
+ ...INFO(proxy),
164
+
165
+ // Sup
166
+ ...SUP(proxy),
167
+
168
+ // One Session
169
+ ...SESSION(proxy),
170
+
171
+ // SSE Stream
172
+ ...SSE(proxy),
173
+
174
+ // LAUNCH Stream
175
+ ...LAUNCH(proxy),
13
176
  };
14
177
 
15
178
  return { state, actions };
@@ -0,0 +1,20 @@
1
+ export default ({ http, chatree }) => {
2
+ return {
3
+ async INFO_GET({ dispatch }, session_id) {
4
+ await dispatch('INFO_UPDATE');
5
+ const { data } = await http.api.sessionHistory({ session_id });
6
+ await dispatch('INFO_UPDATE', data);
7
+ },
8
+
9
+ async INFO_UPDATE({}, info = {}) {
10
+ if (info.history) {
11
+ info.history = chatree(info.history);
12
+ }
13
+ return { info, options: info.params };
14
+ },
15
+
16
+ async INFO_ATTACH({}, { focal, attach, anchor, explicit }) {
17
+ return { focal, attach, anchor, explicit };
18
+ },
19
+ };
20
+ };
@@ -0,0 +1,18 @@
1
+ export default ({ http, url, launch }) => {
2
+ return {
3
+ async LAUNCH_TOKEN({ state }, params = {}) {
4
+ const { data } = await launch.tokenCreate(params);
5
+ return data;
6
+ },
7
+
8
+ async LAUNCH_UPDATE({ state }, { url, mode, query, launch_key }) {
9
+ const { launch } = state;
10
+
11
+ launch.url = `https://launch.billionsintelligence.com/${launch_key}`;
12
+ launch.mode = mode;
13
+ launch.query = query;
14
+
15
+ return { launch };
16
+ },
17
+ };
18
+ };