Skip to content

Commit 489bf78

Browse files
committed
- resolves #1935 [Bot API] Enable web streaming mode
- resolves #1936 [Bot API] Story definition should use suspend/coroutine mode - fixes #1934 [Bot API] websocket mode is broken
1 parent 2d58cfc commit 489bf78

File tree

15 files changed

+308
-81
lines changed

15 files changed

+308
-81
lines changed

CONTRIBUTING.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
To submit a feature or bugfix:
66

77
1. [Create an _issue_](https://github.com/theopenconversationkit/tock/issues/new):
8-
- Reccommended format for the title:
8+
- Recommended format for the title:
99
- `[Component] Title` where component might be
1010
_Studio_, _Core_, _Doc_, etc. and title usually is like _Do or fix something_
1111
2. [Create a _pull request_](https://github.com/theopenconversationkit/tock/pulls) and link it to the issue(s):
@@ -30,4 +30,4 @@ Every new feature or fix should embed its unit test(s).
3030

3131
More about [sources and contrib](https://doc.tock.ai/tock/master/about/contribute.html).
3232

33-
Feel free to [contact us](https://doc.tock.ai/tock/master/about/contact.html).
33+
Feel free to [contact us](https://doc.tock.ai/tock/master/about/contact.html).

bot/admin/web/src/app/configuration/bot-configurations/bot-configurations.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ export class BotConfigurationsComponent implements OnInit, OnDestroy {
7676
}
7777

7878
isFirstLevelConfiguration(bot: BotConfiguration, conf: BotApplicationConfiguration): boolean {
79-
return conf.targetConfigurationId === null;
79+
return conf.targetConfigurationId === null || conf.targetConfigurationId == undefined;
8080
}
8181

8282
prepareCreate(): void {

bot/api/client/src/main/kotlin/ClientBus.kt

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -69,30 +69,34 @@ interface ClientBus : Bus<ClientBus> {
6969

7070
override fun isCompatibleWith(connectorType: ConnectorType) = targetConnectorType == connectorType
7171

72+
suspend fun enableStreaming()
73+
74+
suspend fun disableStreaming()
75+
7276
/**
7377
* Sends a [Card].
7478
*/
75-
fun send(card: Card): ClientBus
79+
suspend fun send(card: Card): ClientBus
7680

7781
/**
7882
* Sends a [Card] as last bot answer.
7983
*/
80-
fun end(card: Card): ClientBus
84+
suspend fun end(card: Card): ClientBus
8185

8286
/**
8387
* Sends a [Carousel].
8488
*/
85-
fun send(carousel: Carousel): ClientBus
89+
suspend fun send(carousel: Carousel): ClientBus
8690

8791
/**
8892
* Sends a [Carousel as last bot answer.
8993
*/
90-
fun end(carousel: Carousel): ClientBus
94+
suspend fun end(carousel: Carousel): ClientBus
9195

9296
/**
9397
* Sends a text with suggestions.
9498
*/
95-
fun send(
99+
suspend fun send(
96100
i18nText: CharSequence,
97101
suggestions: List<Suggestion>,
98102
delay: Long = defaultDelay(currentAnswerIndex),
@@ -102,15 +106,15 @@ interface ClientBus : Bus<ClientBus> {
102106
/**
103107
* Sends a text with suggestions.
104108
*/
105-
fun send(
109+
suspend fun send(
106110
i18nText: CharSequence,
107111
suggestions: List<CharSequence>
108112
): ClientBus = send(i18nText, suggestions.map { Suggestion(translate(it)) })
109113

110114
/**
111115
* Sends a text with suggestions as last bot answer.
112116
*/
113-
fun end(
117+
suspend fun end(
114118
i18nText: CharSequence,
115119
suggestions: List<Suggestion>,
116120
delay: Long = defaultDelay(currentAnswerIndex),
@@ -120,7 +124,7 @@ interface ClientBus : Bus<ClientBus> {
120124
/**
121125
* Sends a text with suggestions as last bot answer.
122126
*/
123-
fun end(
127+
suspend fun end(
124128
i18nText: CharSequence,
125129
suggestions: List<CharSequence>
126130
): ClientBus = end(i18nText, suggestions.map { Suggestion(translate(it)) })

bot/api/client/src/main/kotlin/ClientDefinitionBuilders.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ private fun defaultUnknownStory() = unknownStory { end("Sorry I didn't understan
2626
* @param handler The handler for the story.
2727
*/
2828
fun unknownStory(
29-
handler: (ClientBus).() -> Unit
29+
handler: suspend (ClientBus).() -> Unit
3030
) = ClientStoryDefinition(Intent.unknown, handler = newStoryHandler(handler))
3131

3232
/**
@@ -71,7 +71,7 @@ fun newStory(
7171
secondaryIntents: Set<IntentAware> = emptySet(),
7272
steps: List<ClientStep> = emptyList(),
7373
storyId: String = mainIntent,
74-
handler: (ClientBus).() -> Unit
74+
handler: suspend (ClientBus).() -> Unit
7575
): ClientStoryDefinition =
7676
ClientStoryDefinition(
7777
Intent(mainIntent),
@@ -97,7 +97,7 @@ fun newStory(
9797
secondaryIntents: Set<IntentAware> = emptySet(),
9898
steps: List<ClientStep> = emptyList(),
9999
storyId: String = mainIntent.wrappedIntent().name,
100-
handler: (ClientBus).() -> Unit
100+
handler: suspend (ClientBus).() -> Unit
101101
): ClientStoryDefinition =
102102
ClientStoryDefinition(
103103
mainIntent,
@@ -112,9 +112,9 @@ fun newStory(
112112
* Creates a new [ClientStoryHandler].
113113
* @param handler lamdba handler for the story
114114
*/
115-
fun newStoryHandler(handler: (ClientBus).() -> Unit): ClientStoryHandler =
115+
fun newStoryHandler(handler: suspend (ClientBus).() -> Unit): ClientStoryHandler =
116116
object : ClientStoryHandler {
117-
override fun handle(bus: ClientBus) {
117+
override suspend fun handle(bus: ClientBus) {
118118
handler(bus)
119119
}
120120
}

bot/api/client/src/main/kotlin/ClientStoryHandler.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,5 @@ interface ClientStoryHandler {
2323
*
2424
* @param bus the bus used to get the message and send the answer
2525
*/
26-
fun handle(bus: ClientBus)
26+
suspend fun handle(bus: ClientBus)
2727
}

bot/api/client/src/main/kotlin/TockClientBus.kt

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import ai.tock.bot.api.model.message.bot.Card
2525
import ai.tock.bot.api.model.message.bot.Carousel
2626
import ai.tock.bot.api.model.message.bot.CustomMessage
2727
import ai.tock.bot.api.model.message.bot.Debug
28+
import ai.tock.bot.api.model.message.bot.Event
29+
import ai.tock.bot.api.model.message.bot.EventCategory
2830
import ai.tock.bot.api.model.message.bot.I18nText
2931
import ai.tock.bot.api.model.message.bot.Sentence
3032
import ai.tock.bot.api.model.message.bot.Suggestion
@@ -41,8 +43,8 @@ import ai.tock.translator.I18nLabelValue
4143
import ai.tock.translator.TranslatedSequence
4244
import ai.tock.translator.UserInterfaceType
4345
import ai.tock.translator.raw
46+
import kotlinx.coroutines.runBlocking
4447
import java.util.Locale
45-
import java.util.concurrent.CopyOnWriteArrayList
4648

4749
class TockClientBus(
4850
override val botDefinition: ClientBotDefinition,
@@ -68,7 +70,7 @@ class TockClientBus(
6870
// Target connector : is the connector for which the message is produced
6971
override val targetConnectorType: ConnectorType = request.context.targetConnectorType
7072

71-
override val contextId: String? = request.context.userId.id
73+
override val contextId: String = request.context.userId.id
7274
private var _currentAnswerIndex: Int = 0
7375
override val currentAnswerIndex: Int get() = _currentAnswerIndex
7476
override val entities: MutableList<Entity> = request.entities.toMutableList()
@@ -81,6 +83,12 @@ class TockClientBus(
8183

8284
override val stepName: String? = null
8385

86+
@Volatile
87+
private var streaming: Boolean = false
88+
89+
@Volatile
90+
private var streamedData: String = ""
91+
8492
override fun handle() {
8593
story =
8694
if (request.storyId == botDefinition.unknownStory.storyId) {
@@ -91,11 +99,25 @@ class TockClientBus(
9199
?: botDefinition.unknownStory
92100
}
93101
step = story.steps.find { it.name == request.step }
94-
story.handler.handle(this)
102+
runBlocking {
103+
story.handler.handle(this@TockClientBus)
104+
}
95105
}
96106

97107
override fun defaultDelay(answerIndex: Int): Long = 0
98108

109+
override suspend fun enableStreaming() {
110+
streaming = true
111+
streamedData = ""
112+
addMessage(Event(EventCategory.METADATA, "TOCK_STREAM_RESPONSE", "true"))
113+
}
114+
115+
override suspend fun disableStreaming() {
116+
streaming = false
117+
streamedData = ""
118+
addMessage(Event(EventCategory.METADATA, "TOCK_STREAM_RESPONSE", "false"))
119+
}
120+
99121
private fun addMessage(message: BotMessage?, lastResponse: Boolean = false) {
100122
if (message != null) {
101123
answer(message, lastResponse)
@@ -112,17 +134,33 @@ class TockClientBus(
112134
answer(CustomMessage(ConstrainedValueWrapper(it), delay), lastResponse && plainText == null)
113135
}
114136
if (plainText != null) {
137+
val text : CharSequence = if (streaming) {
138+
streamedData += plainText
139+
streamedData
140+
} else {
141+
plainText
142+
}
143+
115144
answer(
116-
when (plainText) {
117-
is String -> Sentence(I18nText(plainText), delay = delay, suggestions = suggestions)
145+
when (text) {
146+
is String -> Sentence(
147+
I18nText(text = text, toBeTranslated = !streaming),
148+
delay = delay,
149+
suggestions = suggestions
150+
)
151+
152+
is I18nText -> Sentence(text, delay = delay, suggestions = suggestions)
118153
is TranslatedSequence -> Sentence(
119-
I18nText(plainText.toString(), toBeTranslated = false),
154+
I18nText(text.toString(), toBeTranslated = false),
120155
delay = delay,
121156
suggestions = suggestions
122157
)
123158

124-
is I18nText -> Sentence(plainText, delay = delay, suggestions = suggestions)
125-
else -> Sentence(I18nText(plainText.toString()), delay = delay, suggestions = suggestions)
159+
else -> Sentence(
160+
I18nText(text.toString(), toBeTranslated = !streaming),
161+
delay = delay,
162+
suggestions = suggestions
163+
)
126164
},
127165
lastResponse
128166
)
@@ -154,7 +192,7 @@ class TockClientBus(
154192
return this
155193
}
156194

157-
override fun send(
195+
override suspend fun send(
158196
i18nText: CharSequence,
159197
suggestions: List<Suggestion>,
160198
delay: Long,
@@ -164,7 +202,7 @@ class TockClientBus(
164202
return this
165203
}
166204

167-
override fun end(
205+
override suspend fun end(
168206
i18nText: CharSequence,
169207
suggestions: List<Suggestion>,
170208
delay: Long,
@@ -174,12 +212,12 @@ class TockClientBus(
174212
return this
175213
}
176214

177-
override fun end(card: Card): ClientBus {
215+
override suspend fun end(card: Card): ClientBus {
178216
addMessage(card, lastResponse = true)
179217
return this
180218
}
181219

182-
override fun end(carousel: Carousel): ClientBus {
220+
override suspend fun end(carousel: Carousel): ClientBus {
183221
addMessage(carousel, lastResponse = true)
184222
return this
185223
}
@@ -202,12 +240,12 @@ class TockClientBus(
202240
)
203241
}
204242

205-
override fun send(carousel: Carousel): ClientBus {
243+
override suspend fun send(carousel: Carousel): ClientBus {
206244
addMessage(carousel)
207245
return this
208246
}
209247

210-
override fun send(card: Card): ClientBus {
248+
override suspend fun send(card: Card): ClientBus {
211249
addMessage(card)
212250
return this
213251
}

bot/api/model/src/main/kotlin/message/bot/BotMessage.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo
2929
JsonSubTypes.Type(value = Card::class, name = "card"),
3030
JsonSubTypes.Type(value = CustomMessage::class, name = "custom"),
3131
JsonSubTypes.Type(value = Carousel::class, name = "carousel"),
32-
JsonSubTypes.Type(value = Debug::class, name = "debug")
32+
JsonSubTypes.Type(value = Debug::class, name = "debug"),
33+
JsonSubTypes.Type(value = Event::class, name = "event")
3334
)
3435
interface BotMessage {
3536
/**
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright (C) 2017/2025 SNCF Connect & Tech
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package ai.tock.bot.api.model.message.bot
18+
19+
enum class EventCategory {
20+
METADATA
21+
}
22+
23+
data class Event(
24+
val category: EventCategory,
25+
val key: String? = null,
26+
val value: String? = null,
27+
override val delay: Long = 0
28+
) : BotMessage

0 commit comments

Comments
 (0)