Skip to content

Commit

Permalink
Annotate web_client
Browse files Browse the repository at this point in the history
Move hard-coded params to configuration
Update README to include configuration example
Update Docker default configuration
Add Docker automation
  • Loading branch information
NeonDaniel committed Nov 4, 2023
1 parent f2bc4fe commit 5bfe59d
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 41 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/publish_release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@ jobs:
build_and_publish_pypi_and_release:
uses: neongeckocom/.github/.github/workflows/publish_stable_release.yml@master
secrets: inherit
build_and_publish_docker:
needs: build_and_publish_pypi_and_release
uses: neongeckocom/.github/.github/workflows/publish_docker.yml@master
secrets: inherit
6 changes: 5 additions & 1 deletion .github/workflows/publish_test_build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@ on:
- 'neon_iris/version.py'

jobs:
build_and_publish_pypi:
publish_alpha_release:
uses: neongeckocom/.github/.github/workflows/publish_alpha_release.yml@master
secrets: inherit
with:
version_file: "neon_iris/version.py"
setup_py: "setup.py"
build_and_publish_docker:
needs: publish_alpha_release
uses: neongeckocom/.github/.github/workflows/publish_docker.yml@master
secrets: inherit
39 changes: 23 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,31 @@ interacting with Neon systems remotely, via [MQ](https://github.com/NeonGeckoCom
Install the Iris Python package with: `pip install neon-iris`
The `iris` entrypoint is available to interact with a bus via CLI. Help is available via `iris --help`.

## Configuration
Configuration files can be specified via environment variables. By default,
`Iris` will read configuration from `~/.config/neon/diana.yaml` where
`XDG_CONFIG_HOME` is set to the default `~/.config`.
More information about configuration handling can be found
[in the docs](https://neongeckocom.github.io/neon-docs/quick_reference/configuration/).

## Debugging a Diana installation
A default configuration might look like:
```yaml
MQ:
server: neonaialpha.com
port: 25672
users:
mq_handler:
user: neon_api_utils
password: Klatchat2021
iris:
default_lang: en-us
languages:
- en-us
- uk-ua
```
## Interfacing with a Diana installation
The `iris` CLI includes utilities for interacting with a `Diana` backend.

### Configuration
Configuration files can be specified via environment variables. By default,
`Iris` will set default values:
```
OVOS_CONFIG_BASE_FOLDER=neon
OVOS_CONFIG_FILENAME=diana.yaml
```

The example below would override defaults to read configuration from
`~/.config/mycroft/mycroft.conf`.
```
export OVOS_CONFIG_BASE_FOLDER=mycroft
export OVOS_CONFIG_FILENAME=mycroft.conf
```

More information about configuration handling can be found
[in the docs](https://neongeckocom.github.io/neon-docs/quick_reference/configuration/).
12 changes: 12 additions & 0 deletions docker_overlay/etc/neon/diana.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,15 @@ MQ:
mq_handler:
user: neon_api_utils
password: Klatchat2021
iris:
webui_title: Neon AI
webui_description: Chat with Neon
webui_input_placeholder: Ask me something
server_address: "0.0.0.0"
server_port: 7860
default_lang: en-us
languages:
- en-us
- fr-fr
- es-es
- uk-ua
105 changes: 81 additions & 24 deletions neon_iris/web_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from os.path import isfile
from typing import List

import gradio

Expand All @@ -39,40 +40,65 @@

class GradIOClient(NeonAIClient):
def __init__(self, lang: str = None):
self.config = Configuration()
NeonAIClient.__init__(self, self.config.get("MQ"))
config = Configuration()
self.config = config.get('iris') or dict()
NeonAIClient.__init__(self, config.get("MQ"))
self._await_response = Event()
self._response = None
self.lang = lang or self.config.get('lang', 'en-us')
self.lang = lang or self.config.get('default_lang') or \
self.config.get('languages', ['en-us'])[0]
self.chat_ui = gradio.Blocks()

@property
def supported_languages(self):
# TODO Implement lookup in base class
return ['en-us', 'uk-ua']

def update_profile(self, stt_lang, tts_lang, tts_lang_2):
# TODO: Per-client config
def supported_languages(self) -> List[str]:
"""
Get a list of supported languages from configuration
@returns: list of BCP-47 language codes
"""
return self.config.get('languages') or [self.lang]

def update_profile(self, stt_lang: str, tts_lang: str, tts_lang_2: str):
"""
Callback to handle user settings changes from the web UI
"""
# TODO: Per-client config. The current method of referencing
# `self._user_config` means every user shares one configuration which
# does not scale. This client should probably override the
# `self.user_config` property and implement a method for storing user
# configuration in cookies or similar.
profile_update = {"speech": {"stt_language": stt_lang,
"tts_language": tts_lang,
"secondary_tts_language": tts_lang_2}}
from neon_utils.user_utils import apply_local_user_profile_updates
apply_local_user_profile_updates(profile_update, self._user_config)

def on_user_input(self, utterance: str, *args, **kwargs):
def on_user_input(self, utterance: str, *args, **kwargs) -> str:
"""
Callback to handle textual user input
@param utterance: String utterance submitted by the user
@returns: String response from Neon (or "ERROR")
"""
LOG.info(args)
LOG.info(kwargs)
self._await_response.clear()
self._response = None
self.send_utterance(utterance, self.lang)
self._await_response.wait(30)
self._response = self._response or "ERROR"
LOG.info(f"Response={self._response}")
return self._response

def run(self):
title = "Neon AI"
description = "Chat With Neon"
placeholder = "Ask me something"
"""
Blocking method to start the web server
"""
title = self.config.get("webui_title", "Neon AI")
description = self.config.get("webui_description", "Chat With Neon")
placeholder = self.config.get("webui_input_placeholder",
"Ask me something")
address = self.config.get("server_address") or "0.0.0.0"
port = self.config.get("server_port") or 7860

audio_input = gradio.Audio(source="microphone", type="filepath")
chatbot = gradio.Chatbot(label=description)
textbox = gradio.Textbox(placeholder=placeholder)
Expand All @@ -92,14 +118,15 @@ def run(self):
with gradio.Row():
with gradio.Column():
stt_lang = gradio.Radio(label="Input Language",
choices=self.supported_languages,
value=self.lang)
choices=self.supported_languages,
value=self.lang)
tts_lang = gradio.Radio(label="Response Language",
choices=self.supported_languages,
value=self.lang)
tts_lang_2 = gradio.Radio(label="Secondary Response Language",
choices=[None] + self.supported_languages,
value=None)
choices=self.supported_languages,
value=self.lang)
tts_lang_2 = gradio.Radio(label="Second Response Language",
choices=[None] +
self.supported_languages,
value=None)
with gradio.Column():
# TODO: Unit settings
pass
Expand All @@ -111,9 +138,14 @@ def run(self):
pass
submit.click(self.update_profile,
inputs=[stt_lang, tts_lang, tts_lang_2])
blocks.launch(server_name="0.0.0.0", server_port=7860)
blocks.launch(server_name=address, server_port=port)

def handle_klat_response(self, message: Message):
"""
Handle a valid response from Neon. This includes text and base64-encoded
audio in all requested languages.
@param message: Neon response message
"""
LOG.debug(f"Response_data={message.data}")
resp_data = message.data["responses"]
files = []
Expand All @@ -131,17 +163,42 @@ def handle_klat_response(self, message: Message):
self._await_response.set()

def handle_complete_intent_failure(self, message: Message):
"""
Handle an intent failure response from Neon. This should not happen and
indicates the Neon service is probably not yet ready.
@param message: Neon intent failure response message
"""
self._response = "ERROR"
self._await_response.set()

def handle_api_response(self, message: Message):
pass
"""
Catch-all handler for `.response` messages routed to this client that
are not explicitly handled (i.e. get_stt, get_tts)
@param message: Response message to something emitted by this client
"""
LOG.debug(f"Got {message.msg_type}: {message.data}")

def handle_error_response(self, message: Message):
pass
"""
Handle an error response from the MQ service attached to Neon. This
usually indicates a malformed input.
@param message: Response message indicating reason for failure
"""
LOG.error(f"Error response: {message.data}")

def clear_caches(self, message: Message):
pass
"""
Handle a request from Neon to clear cached data.
@param message: Message requesting cache deletion. The context of this
message will include the requesting user for user-specific caches
"""
# TODO: remove cached TTS audio responses

def clear_media(self, message: Message):
"""
Handle a request from Neon to clear local multimedia. This method does
not apply to this client as there is no user-generated media to clear.
@param message: Message requesting media deletion
"""
pass

0 comments on commit 5bfe59d

Please sign in to comment.