Merge pull request #1 from sasan-j/feature/refactor
Browse files- .devcontainer/Dockerfile +2 -2
- .gitignore +4 -0
- .pylintrc +642 -0
- apis.py +0 -285
- car_assistant_eta.ipynb +0 -0
- car_assistant_slim.ipynb +41 -12
- car_assistant_text.ipynb +0 -0
- core/__init__.py +199 -0
- kitt.py +331 -0
- requirements.txt +7 -2
- skills/__init__.py +43 -0
- skills/common.py +62 -0
- skills/poi.py +143 -0
- skills/routing.py +167 -0
- skills/vehicle.py +35 -0
- skills/weather.py +113 -0
- stt.ipynb +292 -0
- tts.ipynb +0 -0
.devcontainer/Dockerfile
CHANGED
@@ -2,8 +2,8 @@
|
|
2 |
# Run: sudo docker run -v $(pwd):/workspace/project --gpus all -it --rm <project_name>
|
3 |
|
4 |
ARG USERNAME=kitt
|
5 |
-
ARG USER_UID=
|
6 |
-
ARG USER_GID=
|
7 |
|
8 |
|
9 |
FROM nvidia/cuda:12.1.1-base-ubuntu22.04
|
|
|
2 |
# Run: sudo docker run -v $(pwd):/workspace/project --gpus all -it --rm <project_name>
|
3 |
|
4 |
ARG USERNAME=kitt
|
5 |
+
ARG USER_UID=320869193
|
6 |
+
ARG USER_GID=1429829944
|
7 |
|
8 |
|
9 |
FROM nvidia/cuda:12.1.1-base-ubuntu22.04
|
.gitignore
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
audio
|
2 |
+
.DS_Store
|
3 |
+
.env
|
4 |
+
__pycache__
|
.pylintrc
ADDED
@@ -0,0 +1,642 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
[MAIN]
|
2 |
+
|
3 |
+
# Analyse import fallback blocks. This can be used to support both Python 2 and
|
4 |
+
# 3 compatible code, which means that the block might have code that exists
|
5 |
+
# only in one or another interpreter, leading to false positives when analysed.
|
6 |
+
analyse-fallback-blocks=no
|
7 |
+
|
8 |
+
# Clear in-memory caches upon conclusion of linting. Useful if running pylint
|
9 |
+
# in a server-like mode.
|
10 |
+
clear-cache-post-run=no
|
11 |
+
|
12 |
+
# Load and enable all available extensions. Use --list-extensions to see a list
|
13 |
+
# all available extensions.
|
14 |
+
#enable-all-extensions=
|
15 |
+
|
16 |
+
# In error mode, messages with a category besides ERROR or FATAL are
|
17 |
+
# suppressed, and no reports are done by default. Error mode is compatible with
|
18 |
+
# disabling specific errors.
|
19 |
+
#errors-only=
|
20 |
+
|
21 |
+
# Always return a 0 (non-error) status code, even if lint errors are found.
|
22 |
+
# This is primarily useful in continuous integration scripts.
|
23 |
+
#exit-zero=
|
24 |
+
|
25 |
+
# A comma-separated list of package or module names from where C extensions may
|
26 |
+
# be loaded. Extensions are loading into the active Python interpreter and may
|
27 |
+
# run arbitrary code.
|
28 |
+
extension-pkg-allow-list=
|
29 |
+
|
30 |
+
# A comma-separated list of package or module names from where C extensions may
|
31 |
+
# be loaded. Extensions are loading into the active Python interpreter and may
|
32 |
+
# run arbitrary code. (This is an alternative name to extension-pkg-allow-list
|
33 |
+
# for backward compatibility.)
|
34 |
+
extension-pkg-whitelist=
|
35 |
+
|
36 |
+
# Return non-zero exit code if any of these messages/categories are detected,
|
37 |
+
# even if score is above --fail-under value. Syntax same as enable. Messages
|
38 |
+
# specified are enabled, while categories only check already-enabled messages.
|
39 |
+
fail-on=
|
40 |
+
|
41 |
+
# Specify a score threshold under which the program will exit with error.
|
42 |
+
fail-under=10
|
43 |
+
|
44 |
+
# Interpret the stdin as a python script, whose filename needs to be passed as
|
45 |
+
# the module_or_package argument.
|
46 |
+
#from-stdin=
|
47 |
+
|
48 |
+
# Files or directories to be skipped. They should be base names, not paths.
|
49 |
+
ignore=CVS
|
50 |
+
|
51 |
+
# Add files or directories matching the regular expressions patterns to the
|
52 |
+
# ignore-list. The regex matches against paths and can be in Posix or Windows
|
53 |
+
# format. Because '\\' represents the directory delimiter on Windows systems,
|
54 |
+
# it can't be used as an escape character.
|
55 |
+
ignore-paths=
|
56 |
+
|
57 |
+
# Files or directories matching the regular expression patterns are skipped.
|
58 |
+
# The regex matches against base names, not paths. The default value ignores
|
59 |
+
# Emacs file locks
|
60 |
+
ignore-patterns=^\.#
|
61 |
+
|
62 |
+
# List of module names for which member attributes should not be checked
|
63 |
+
# (useful for modules/projects where namespaces are manipulated during runtime
|
64 |
+
# and thus existing member attributes cannot be deduced by static analysis). It
|
65 |
+
# supports qualified module names, as well as Unix pattern matching.
|
66 |
+
ignored-modules=
|
67 |
+
|
68 |
+
# Python code to execute, usually for sys.path manipulation such as
|
69 |
+
# pygtk.require().
|
70 |
+
#init-hook=
|
71 |
+
|
72 |
+
# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the
|
73 |
+
# number of processors available to use, and will cap the count on Windows to
|
74 |
+
# avoid hangs.
|
75 |
+
jobs=1
|
76 |
+
|
77 |
+
# Control the amount of potential inferred values when inferring a single
|
78 |
+
# object. This can help the performance when dealing with large functions or
|
79 |
+
# complex, nested conditions.
|
80 |
+
limit-inference-results=100
|
81 |
+
|
82 |
+
# List of plugins (as comma separated values of python module names) to load,
|
83 |
+
# usually to register additional checkers.
|
84 |
+
load-plugins=
|
85 |
+
|
86 |
+
# Pickle collected data for later comparisons.
|
87 |
+
persistent=yes
|
88 |
+
|
89 |
+
# Minimum Python version to use for version dependent checks. Will default to
|
90 |
+
# the version used to run pylint.
|
91 |
+
py-version=3.12
|
92 |
+
|
93 |
+
# Discover python modules and packages in the file system subtree.
|
94 |
+
recursive=no
|
95 |
+
|
96 |
+
# Add paths to the list of the source roots. Supports globbing patterns. The
|
97 |
+
# source root is an absolute path or a path relative to the current working
|
98 |
+
# directory used to determine a package namespace for modules located under the
|
99 |
+
# source root.
|
100 |
+
source-roots=
|
101 |
+
|
102 |
+
# When enabled, pylint would attempt to guess common misconfiguration and emit
|
103 |
+
# user-friendly hints instead of false-positive error messages.
|
104 |
+
suggestion-mode=yes
|
105 |
+
|
106 |
+
# Allow loading of arbitrary C extensions. Extensions are imported into the
|
107 |
+
# active Python interpreter and may run arbitrary code.
|
108 |
+
unsafe-load-any-extension=no
|
109 |
+
|
110 |
+
# In verbose mode, extra non-checker-related info will be displayed.
|
111 |
+
#verbose=
|
112 |
+
|
113 |
+
|
114 |
+
[BASIC]
|
115 |
+
|
116 |
+
# Naming style matching correct argument names.
|
117 |
+
argument-naming-style=snake_case
|
118 |
+
|
119 |
+
# Regular expression matching correct argument names. Overrides argument-
|
120 |
+
# naming-style. If left empty, argument names will be checked with the set
|
121 |
+
# naming style.
|
122 |
+
#argument-rgx=
|
123 |
+
|
124 |
+
# Naming style matching correct attribute names.
|
125 |
+
attr-naming-style=snake_case
|
126 |
+
|
127 |
+
# Regular expression matching correct attribute names. Overrides attr-naming-
|
128 |
+
# style. If left empty, attribute names will be checked with the set naming
|
129 |
+
# style.
|
130 |
+
#attr-rgx=
|
131 |
+
|
132 |
+
# Bad variable names which should always be refused, separated by a comma.
|
133 |
+
bad-names=foo,
|
134 |
+
bar,
|
135 |
+
baz,
|
136 |
+
toto,
|
137 |
+
tutu,
|
138 |
+
tata
|
139 |
+
|
140 |
+
# Bad variable names regexes, separated by a comma. If names match any regex,
|
141 |
+
# they will always be refused
|
142 |
+
bad-names-rgxs=
|
143 |
+
|
144 |
+
# Naming style matching correct class attribute names.
|
145 |
+
class-attribute-naming-style=any
|
146 |
+
|
147 |
+
# Regular expression matching correct class attribute names. Overrides class-
|
148 |
+
# attribute-naming-style. If left empty, class attribute names will be checked
|
149 |
+
# with the set naming style.
|
150 |
+
#class-attribute-rgx=
|
151 |
+
|
152 |
+
# Naming style matching correct class constant names.
|
153 |
+
class-const-naming-style=UPPER_CASE
|
154 |
+
|
155 |
+
# Regular expression matching correct class constant names. Overrides class-
|
156 |
+
# const-naming-style. If left empty, class constant names will be checked with
|
157 |
+
# the set naming style.
|
158 |
+
#class-const-rgx=
|
159 |
+
|
160 |
+
# Naming style matching correct class names.
|
161 |
+
class-naming-style=PascalCase
|
162 |
+
|
163 |
+
# Regular expression matching correct class names. Overrides class-naming-
|
164 |
+
# style. If left empty, class names will be checked with the set naming style.
|
165 |
+
#class-rgx=
|
166 |
+
|
167 |
+
# Naming style matching correct constant names.
|
168 |
+
const-naming-style=UPPER_CASE
|
169 |
+
|
170 |
+
# Regular expression matching correct constant names. Overrides const-naming-
|
171 |
+
# style. If left empty, constant names will be checked with the set naming
|
172 |
+
# style.
|
173 |
+
#const-rgx=
|
174 |
+
|
175 |
+
# Minimum line length for functions/classes that require docstrings, shorter
|
176 |
+
# ones are exempt.
|
177 |
+
docstring-min-length=-1
|
178 |
+
|
179 |
+
# Naming style matching correct function names.
|
180 |
+
function-naming-style=snake_case
|
181 |
+
|
182 |
+
# Regular expression matching correct function names. Overrides function-
|
183 |
+
# naming-style. If left empty, function names will be checked with the set
|
184 |
+
# naming style.
|
185 |
+
#function-rgx=
|
186 |
+
|
187 |
+
# Good variable names which should always be accepted, separated by a comma.
|
188 |
+
good-names=i,
|
189 |
+
j,
|
190 |
+
k,
|
191 |
+
ex,
|
192 |
+
Run,
|
193 |
+
_
|
194 |
+
|
195 |
+
# Good variable names regexes, separated by a comma. If names match any regex,
|
196 |
+
# they will always be accepted
|
197 |
+
good-names-rgxs=
|
198 |
+
|
199 |
+
# Include a hint for the correct naming format with invalid-name.
|
200 |
+
include-naming-hint=no
|
201 |
+
|
202 |
+
# Naming style matching correct inline iteration names.
|
203 |
+
inlinevar-naming-style=any
|
204 |
+
|
205 |
+
# Regular expression matching correct inline iteration names. Overrides
|
206 |
+
# inlinevar-naming-style. If left empty, inline iteration names will be checked
|
207 |
+
# with the set naming style.
|
208 |
+
#inlinevar-rgx=
|
209 |
+
|
210 |
+
# Naming style matching correct method names.
|
211 |
+
method-naming-style=snake_case
|
212 |
+
|
213 |
+
# Regular expression matching correct method names. Overrides method-naming-
|
214 |
+
# style. If left empty, method names will be checked with the set naming style.
|
215 |
+
#method-rgx=
|
216 |
+
|
217 |
+
# Naming style matching correct module names.
|
218 |
+
module-naming-style=snake_case
|
219 |
+
|
220 |
+
# Regular expression matching correct module names. Overrides module-naming-
|
221 |
+
# style. If left empty, module names will be checked with the set naming style.
|
222 |
+
#module-rgx=
|
223 |
+
|
224 |
+
# Colon-delimited sets of names that determine each other's naming style when
|
225 |
+
# the name regexes allow several styles.
|
226 |
+
name-group=
|
227 |
+
|
228 |
+
# Regular expression which should only match function or class names that do
|
229 |
+
# not require a docstring.
|
230 |
+
no-docstring-rgx=^_
|
231 |
+
|
232 |
+
# List of decorators that produce properties, such as abc.abstractproperty. Add
|
233 |
+
# to this list to register other decorators that produce valid properties.
|
234 |
+
# These decorators are taken in consideration only for invalid-name.
|
235 |
+
property-classes=abc.abstractproperty
|
236 |
+
|
237 |
+
# Regular expression matching correct type alias names. If left empty, type
|
238 |
+
# alias names will be checked with the set naming style.
|
239 |
+
#typealias-rgx=
|
240 |
+
|
241 |
+
# Regular expression matching correct type variable names. If left empty, type
|
242 |
+
# variable names will be checked with the set naming style.
|
243 |
+
#typevar-rgx=
|
244 |
+
|
245 |
+
# Naming style matching correct variable names.
|
246 |
+
variable-naming-style=snake_case
|
247 |
+
|
248 |
+
# Regular expression matching correct variable names. Overrides variable-
|
249 |
+
# naming-style. If left empty, variable names will be checked with the set
|
250 |
+
# naming style.
|
251 |
+
#variable-rgx=
|
252 |
+
|
253 |
+
|
254 |
+
[CLASSES]
|
255 |
+
|
256 |
+
# Warn about protected attribute access inside special methods
|
257 |
+
check-protected-access-in-special-methods=no
|
258 |
+
|
259 |
+
# List of method names used to declare (i.e. assign) instance attributes.
|
260 |
+
defining-attr-methods=__init__,
|
261 |
+
__new__,
|
262 |
+
setUp,
|
263 |
+
asyncSetUp,
|
264 |
+
__post_init__
|
265 |
+
|
266 |
+
# List of member names, which should be excluded from the protected access
|
267 |
+
# warning.
|
268 |
+
exclude-protected=_asdict,_fields,_replace,_source,_make,os._exit
|
269 |
+
|
270 |
+
# List of valid names for the first argument in a class method.
|
271 |
+
valid-classmethod-first-arg=cls
|
272 |
+
|
273 |
+
# List of valid names for the first argument in a metaclass class method.
|
274 |
+
valid-metaclass-classmethod-first-arg=mcs
|
275 |
+
|
276 |
+
|
277 |
+
[DESIGN]
|
278 |
+
|
279 |
+
# List of regular expressions of class ancestor names to ignore when counting
|
280 |
+
# public methods (see R0903)
|
281 |
+
exclude-too-few-public-methods=
|
282 |
+
|
283 |
+
# List of qualified class names to ignore when counting class parents (see
|
284 |
+
# R0901)
|
285 |
+
ignored-parents=
|
286 |
+
|
287 |
+
# Maximum number of arguments for function / method.
|
288 |
+
max-args=5
|
289 |
+
|
290 |
+
# Maximum number of attributes for a class (see R0902).
|
291 |
+
max-attributes=7
|
292 |
+
|
293 |
+
# Maximum number of boolean expressions in an if statement (see R0916).
|
294 |
+
max-bool-expr=5
|
295 |
+
|
296 |
+
# Maximum number of branch for function / method body.
|
297 |
+
max-branches=12
|
298 |
+
|
299 |
+
# Maximum number of locals for function / method body.
|
300 |
+
max-locals=15
|
301 |
+
|
302 |
+
# Maximum number of parents for a class (see R0901).
|
303 |
+
max-parents=7
|
304 |
+
|
305 |
+
# Maximum number of public methods for a class (see R0904).
|
306 |
+
max-public-methods=20
|
307 |
+
|
308 |
+
# Maximum number of return / yield for function / method body.
|
309 |
+
max-returns=6
|
310 |
+
|
311 |
+
# Maximum number of statements in function / method body.
|
312 |
+
max-statements=50
|
313 |
+
|
314 |
+
# Minimum number of public methods for a class (see R0903).
|
315 |
+
min-public-methods=2
|
316 |
+
|
317 |
+
|
318 |
+
[EXCEPTIONS]
|
319 |
+
|
320 |
+
# Exceptions that will emit a warning when caught.
|
321 |
+
overgeneral-exceptions=builtins.BaseException,builtins.Exception
|
322 |
+
|
323 |
+
|
324 |
+
[FORMAT]
|
325 |
+
|
326 |
+
# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
|
327 |
+
expected-line-ending-format=
|
328 |
+
|
329 |
+
# Regexp for a line that is allowed to be longer than the limit.
|
330 |
+
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
|
331 |
+
|
332 |
+
# Number of spaces of indent required inside a hanging or continued line.
|
333 |
+
indent-after-paren=4
|
334 |
+
|
335 |
+
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
|
336 |
+
# tab).
|
337 |
+
indent-string=' '
|
338 |
+
|
339 |
+
# Maximum number of characters on a single line.
|
340 |
+
max-line-length=100
|
341 |
+
|
342 |
+
# Maximum number of lines in a module.
|
343 |
+
max-module-lines=1000
|
344 |
+
|
345 |
+
# Allow the body of a class to be on the same line as the declaration if body
|
346 |
+
# contains single statement.
|
347 |
+
single-line-class-stmt=no
|
348 |
+
|
349 |
+
# Allow the body of an if to be on the same line as the test if there is no
|
350 |
+
# else.
|
351 |
+
single-line-if-stmt=no
|
352 |
+
|
353 |
+
|
354 |
+
[IMPORTS]
|
355 |
+
|
356 |
+
# List of modules that can be imported at any level, not just the top level
|
357 |
+
# one.
|
358 |
+
allow-any-import-level=
|
359 |
+
|
360 |
+
# Allow explicit reexports by alias from a package __init__.
|
361 |
+
allow-reexport-from-package=no
|
362 |
+
|
363 |
+
# Allow wildcard imports from modules that define __all__.
|
364 |
+
allow-wildcard-with-all=no
|
365 |
+
|
366 |
+
# Deprecated modules which should not be used, separated by a comma.
|
367 |
+
deprecated-modules=
|
368 |
+
|
369 |
+
# Output a graph (.gv or any supported image format) of external dependencies
|
370 |
+
# to the given file (report RP0402 must not be disabled).
|
371 |
+
ext-import-graph=
|
372 |
+
|
373 |
+
# Output a graph (.gv or any supported image format) of all (i.e. internal and
|
374 |
+
# external) dependencies to the given file (report RP0402 must not be
|
375 |
+
# disabled).
|
376 |
+
import-graph=
|
377 |
+
|
378 |
+
# Output a graph (.gv or any supported image format) of internal dependencies
|
379 |
+
# to the given file (report RP0402 must not be disabled).
|
380 |
+
int-import-graph=
|
381 |
+
|
382 |
+
# Force import order to recognize a module as part of the standard
|
383 |
+
# compatibility libraries.
|
384 |
+
known-standard-library=
|
385 |
+
|
386 |
+
# Force import order to recognize a module as part of a third party library.
|
387 |
+
known-third-party=enchant
|
388 |
+
|
389 |
+
# Couples of modules and preferred modules, separated by a comma.
|
390 |
+
preferred-modules=
|
391 |
+
|
392 |
+
|
393 |
+
[LOGGING]
|
394 |
+
|
395 |
+
# The type of string formatting that logging methods do. `old` means using %
|
396 |
+
# formatting, `new` is for `{}` formatting.
|
397 |
+
logging-format-style=old
|
398 |
+
|
399 |
+
# Logging modules to check that the string format arguments are in logging
|
400 |
+
# function parameter format.
|
401 |
+
logging-modules=logging
|
402 |
+
|
403 |
+
|
404 |
+
[MESSAGES CONTROL]
|
405 |
+
|
406 |
+
# Only show warnings with the listed confidence levels. Leave empty to show
|
407 |
+
# all. Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE,
|
408 |
+
# UNDEFINED.
|
409 |
+
confidence=HIGH,
|
410 |
+
CONTROL_FLOW,
|
411 |
+
INFERENCE,
|
412 |
+
INFERENCE_FAILURE,
|
413 |
+
UNDEFINED
|
414 |
+
|
415 |
+
# Disable the message, report, category or checker with the given id(s). You
|
416 |
+
# can either give multiple identifiers separated by comma (,) or put this
|
417 |
+
# option multiple times (only on the command line, not in the configuration
|
418 |
+
# file where it should appear only once). You can also use "--disable=all" to
|
419 |
+
# disable everything first and then re-enable specific checks. For example, if
|
420 |
+
# you want to run only the similarities checker, you can use "--disable=all
|
421 |
+
# --enable=similarities". If you want to run only the classes checker, but have
|
422 |
+
# no Warning level messages displayed, use "--disable=all --enable=classes
|
423 |
+
# --disable=W".
|
424 |
+
disable=raw-checker-failed,
|
425 |
+
bad-inline-option,
|
426 |
+
locally-disabled,
|
427 |
+
file-ignored,
|
428 |
+
suppressed-message,
|
429 |
+
useless-suppression,
|
430 |
+
deprecated-pragma,
|
431 |
+
use-symbolic-message-instead,
|
432 |
+
use-implicit-booleaness-not-comparison-to-string,
|
433 |
+
use-implicit-booleaness-not-comparison-to-zero,
|
434 |
+
missing-module-docstring,
|
435 |
+
missing-class-docstring,
|
436 |
+
missing-function-docstring
|
437 |
+
|
438 |
+
# Enable the message, report, category or checker with the given id(s). You can
|
439 |
+
# either give multiple identifier separated by comma (,) or put this option
|
440 |
+
# multiple time (only on the command line, not in the configuration file where
|
441 |
+
# it should appear only once). See also the "--disable" option for examples.
|
442 |
+
enable=
|
443 |
+
|
444 |
+
|
445 |
+
[METHOD_ARGS]
|
446 |
+
|
447 |
+
# List of qualified names (i.e., library.method) which require a timeout
|
448 |
+
# parameter e.g. 'requests.api.get,requests.api.post'
|
449 |
+
timeout-methods=requests.api.delete,requests.api.get,requests.api.head,requests.api.options,requests.api.patch,requests.api.post,requests.api.put,requests.api.request
|
450 |
+
|
451 |
+
|
452 |
+
[MISCELLANEOUS]
|
453 |
+
|
454 |
+
# List of note tags to take in consideration, separated by a comma.
|
455 |
+
notes=FIXME,
|
456 |
+
XXX,
|
457 |
+
TODO
|
458 |
+
|
459 |
+
# Regular expression of note tags to take in consideration.
|
460 |
+
notes-rgx=
|
461 |
+
|
462 |
+
|
463 |
+
[REFACTORING]
|
464 |
+
|
465 |
+
# Maximum number of nested blocks for function / method body
|
466 |
+
max-nested-blocks=5
|
467 |
+
|
468 |
+
# Complete name of functions that never returns. When checking for
|
469 |
+
# inconsistent-return-statements if a never returning function is called then
|
470 |
+
# it will be considered as an explicit return statement and no message will be
|
471 |
+
# printed.
|
472 |
+
never-returning-functions=sys.exit,argparse.parse_error
|
473 |
+
|
474 |
+
# Let 'consider-using-join' be raised when the separator to join on would be
|
475 |
+
# non-empty (resulting in expected fixes of the type: ``"- " + " -
|
476 |
+
# ".join(items)``)
|
477 |
+
# suggest-join-with-non-empty-separator=yes
|
478 |
+
|
479 |
+
|
480 |
+
[REPORTS]
|
481 |
+
|
482 |
+
# Python expression which should return a score less than or equal to 10. You
|
483 |
+
# have access to the variables 'fatal', 'error', 'warning', 'refactor',
|
484 |
+
# 'convention', and 'info' which contain the number of messages in each
|
485 |
+
# category, as well as 'statement' which is the total number of statements
|
486 |
+
# analyzed. This score is used by the global evaluation report (RP0004).
|
487 |
+
evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10))
|
488 |
+
|
489 |
+
# Template used to display messages. This is a python new-style format string
|
490 |
+
# used to format the message information. See doc for all details.
|
491 |
+
msg-template=
|
492 |
+
|
493 |
+
# Set the output format. Available formats are: text, parseable, colorized,
|
494 |
+
# json2 (improved json format), json (old json format) and msvs (visual
|
495 |
+
# studio). You can also give a reporter class, e.g.
|
496 |
+
# mypackage.mymodule.MyReporterClass.
|
497 |
+
#output-format=
|
498 |
+
|
499 |
+
# Tells whether to display a full report or only the messages.
|
500 |
+
reports=no
|
501 |
+
|
502 |
+
# Activate the evaluation score.
|
503 |
+
score=yes
|
504 |
+
|
505 |
+
|
506 |
+
[SIMILARITIES]
|
507 |
+
|
508 |
+
# Comments are removed from the similarity computation
|
509 |
+
ignore-comments=yes
|
510 |
+
|
511 |
+
# Docstrings are removed from the similarity computation
|
512 |
+
ignore-docstrings=yes
|
513 |
+
|
514 |
+
# Imports are removed from the similarity computation
|
515 |
+
ignore-imports=yes
|
516 |
+
|
517 |
+
# Signatures are removed from the similarity computation
|
518 |
+
ignore-signatures=yes
|
519 |
+
|
520 |
+
# Minimum lines number of a similarity.
|
521 |
+
min-similarity-lines=4
|
522 |
+
|
523 |
+
|
524 |
+
[SPELLING]
|
525 |
+
|
526 |
+
# Limits count of emitted suggestions for spelling mistakes.
|
527 |
+
max-spelling-suggestions=4
|
528 |
+
|
529 |
+
# Spelling dictionary name. No available dictionaries : You need to install
|
530 |
+
# both the python package and the system dependency for enchant to work.
|
531 |
+
spelling-dict=
|
532 |
+
|
533 |
+
# List of comma separated words that should be considered directives if they
|
534 |
+
# appear at the beginning of a comment and should not be checked.
|
535 |
+
spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy:
|
536 |
+
|
537 |
+
# List of comma separated words that should not be checked.
|
538 |
+
spelling-ignore-words=
|
539 |
+
|
540 |
+
# A path to a file that contains the private dictionary; one word per line.
|
541 |
+
spelling-private-dict-file=
|
542 |
+
|
543 |
+
# Tells whether to store unknown words to the private dictionary (see the
|
544 |
+
# --spelling-private-dict-file option) instead of raising a message.
|
545 |
+
spelling-store-unknown-words=no
|
546 |
+
|
547 |
+
|
548 |
+
[STRING]
|
549 |
+
|
550 |
+
# This flag controls whether inconsistent-quotes generates a warning when the
|
551 |
+
# character used as a quote delimiter is used inconsistently within a module.
|
552 |
+
check-quote-consistency=no
|
553 |
+
|
554 |
+
# This flag controls whether the implicit-str-concat should generate a warning
|
555 |
+
# on implicit string concatenation in sequences defined over several lines.
|
556 |
+
check-str-concat-over-line-jumps=no
|
557 |
+
|
558 |
+
|
559 |
+
[TYPECHECK]
|
560 |
+
|
561 |
+
# List of decorators that produce context managers, such as
|
562 |
+
# contextlib.contextmanager. Add to this list to register other decorators that
|
563 |
+
# produce valid context managers.
|
564 |
+
contextmanager-decorators=contextlib.contextmanager
|
565 |
+
|
566 |
+
# List of members which are set dynamically and missed by pylint inference
|
567 |
+
# system, and so shouldn't trigger E1101 when accessed. Python regular
|
568 |
+
# expressions are accepted.
|
569 |
+
generated-members=
|
570 |
+
|
571 |
+
# Tells whether to warn about missing members when the owner of the attribute
|
572 |
+
# is inferred to be None.
|
573 |
+
ignore-none=yes
|
574 |
+
|
575 |
+
# This flag controls whether pylint should warn about no-member and similar
|
576 |
+
# checks whenever an opaque object is returned when inferring. The inference
|
577 |
+
# can return multiple potential results while evaluating a Python object, but
|
578 |
+
# some branches might not be evaluated, which results in partial inference. In
|
579 |
+
# that case, it might be useful to still emit no-member and other checks for
|
580 |
+
# the rest of the inferred objects.
|
581 |
+
ignore-on-opaque-inference=yes
|
582 |
+
|
583 |
+
# List of symbolic message names to ignore for Mixin members.
|
584 |
+
ignored-checks-for-mixins=no-member,
|
585 |
+
not-async-context-manager,
|
586 |
+
not-context-manager,
|
587 |
+
attribute-defined-outside-init
|
588 |
+
|
589 |
+
# List of class names for which member attributes should not be checked (useful
|
590 |
+
# for classes with dynamically set attributes). This supports the use of
|
591 |
+
# qualified names.
|
592 |
+
ignored-classes=optparse.Values,thread._local,_thread._local,argparse.Namespace
|
593 |
+
|
594 |
+
# Show a hint with possible names when a member name was not found. The aspect
|
595 |
+
# of finding the hint is based on edit distance.
|
596 |
+
missing-member-hint=yes
|
597 |
+
|
598 |
+
# The minimum edit distance a name should have in order to be considered a
|
599 |
+
# similar match for a missing member name.
|
600 |
+
missing-member-hint-distance=1
|
601 |
+
|
602 |
+
# The total number of similar names that should be taken in consideration when
|
603 |
+
# showing a hint for a missing member.
|
604 |
+
missing-member-max-choices=1
|
605 |
+
|
606 |
+
# Regex pattern to define which classes are considered mixins.
|
607 |
+
mixin-class-rgx=.*[Mm]ixin
|
608 |
+
|
609 |
+
# List of decorators that change the signature of a decorated function.
|
610 |
+
signature-mutators=
|
611 |
+
|
612 |
+
|
613 |
+
[VARIABLES]
|
614 |
+
|
615 |
+
# List of additional names supposed to be defined in builtins. Remember that
|
616 |
+
# you should avoid defining new builtins when possible.
|
617 |
+
additional-builtins=
|
618 |
+
|
619 |
+
# Tells whether unused global variables should be treated as a violation.
|
620 |
+
allow-global-unused-variables=yes
|
621 |
+
|
622 |
+
# List of names allowed to shadow builtins
|
623 |
+
allowed-redefined-builtins=
|
624 |
+
|
625 |
+
# List of strings which can identify a callback function by name. A callback
|
626 |
+
# name must start or end with one of those strings.
|
627 |
+
callbacks=cb_,
|
628 |
+
_cb
|
629 |
+
|
630 |
+
# A regular expression matching the name of dummy variables (i.e. expected to
|
631 |
+
# not be used).
|
632 |
+
dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_
|
633 |
+
|
634 |
+
# Argument names that match this expression will be ignored.
|
635 |
+
ignored-argument-names=_.*|^ignored_|^unused_
|
636 |
+
|
637 |
+
# Tells whether we should check for unused import in __init__ files.
|
638 |
+
init-import=no
|
639 |
+
|
640 |
+
# List of qualified module names which can have objects that can redefine
|
641 |
+
# builtins.
|
642 |
+
redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io
|
apis.py
CHANGED
@@ -1,5 +1,3 @@
|
|
1 |
-
import requests
|
2 |
-
|
3 |
from geopy.geocoders import Nominatim
|
4 |
|
5 |
|
@@ -53,287 +51,4 @@ def check_city_coordinates(lat = "", lon = "", city = "", **kwargs):
|
|
53 |
lon = coord.longitude
|
54 |
return lat, lon, city
|
55 |
|
56 |
-
# Select coordinates at equal distance, including the last one
|
57 |
-
def select_equally_spaced_coordinates(coords, number_of_points=10):
|
58 |
-
n = len(coords)
|
59 |
-
selected_coords = []
|
60 |
-
interval = max((n - 1) / (number_of_points - 1), 1)
|
61 |
-
for i in range(number_of_points):
|
62 |
-
# Calculate the index, ensuring it doesn't exceed the bounds of the list
|
63 |
-
index = int(round(i * interval))
|
64 |
-
if index < n:
|
65 |
-
selected_coords.append(coords[index])
|
66 |
-
return selected_coords
|
67 |
-
|
68 |
-
def find_points_of_interest(lat="0", lon="0", city="", type_of_poi="restaurant", **kwargs):
|
69 |
-
"""
|
70 |
-
Return some of the closest points of interest for a specific location and type of point of interest. The more parameters there are, the more precise.
|
71 |
-
:param lat (string): latitude
|
72 |
-
:param lon (string): longitude
|
73 |
-
:param city (string): Required. city
|
74 |
-
:param type_of_poi (string): Required. type of point of interest depending on what the user wants to do.
|
75 |
-
"""
|
76 |
-
lat, lon, city = check_city_coordinates(lat,lon,city)
|
77 |
-
|
78 |
-
r = requests.get(f'https://api.tomtom.com/search/2/search/{type_of_poi}'
|
79 |
-
'.json?key={0}&lat={1}&lon={2}&radius=10000&idxSet=POI&limit=100'.format(
|
80 |
-
TOMTOM_KEY,
|
81 |
-
lat,
|
82 |
-
lon
|
83 |
-
))
|
84 |
-
|
85 |
-
# Parse JSON from the response
|
86 |
-
data = r.json()
|
87 |
-
#print(data)
|
88 |
-
# Extract results
|
89 |
-
results = data['results']
|
90 |
-
|
91 |
-
# Sort the results based on distance
|
92 |
-
sorted_results = sorted(results, key=lambda x: x['dist'])
|
93 |
-
#print(sorted_results)
|
94 |
-
|
95 |
-
# Format and limit to top 5 results
|
96 |
-
formatted_results = [
|
97 |
-
f"The {type_of_poi} {result['poi']['name']} is {int(result['dist'])} meters away"
|
98 |
-
for result in sorted_results[:5]
|
99 |
-
]
|
100 |
-
|
101 |
-
|
102 |
-
return ". ".join(formatted_results)
|
103 |
-
|
104 |
-
def find_route(lat_depart="0", lon_depart="0", city_depart="", address_destination="", depart_time ="", **kwargs):
|
105 |
-
"""
|
106 |
-
Return the distance and the estimated time to go to a specific destination from the current place, at a specified depart time.
|
107 |
-
:param lat_depart (string): latitude of depart
|
108 |
-
:param lon_depart (string): longitude of depart
|
109 |
-
:param city_depart (string): Required. city of depart
|
110 |
-
:param address_destination (string): Required. The destination
|
111 |
-
:param depart_time (string): departure hour, in the format '08:00:20'.
|
112 |
-
"""
|
113 |
-
print(address_destination)
|
114 |
-
date = "2025-03-29T"
|
115 |
-
departure_time = '2024-02-01T' + depart_time
|
116 |
-
lat, lon, city = check_city_coordinates(lat_depart,lon_depart,city_depart)
|
117 |
-
lat_dest, lon_dest = find_coordinates(address_destination)
|
118 |
-
#print(lat_dest, lon_dest)
|
119 |
-
|
120 |
-
#print(departure_time)
|
121 |
-
|
122 |
-
r = requests.get('https://api.tomtom.com/routing/1/calculateRoute/{0},{1}:{2},{3}/json?key={4}&departAt={5}'.format(
|
123 |
-
lat_depart,
|
124 |
-
lon_depart,
|
125 |
-
lat_dest,
|
126 |
-
lon_dest,
|
127 |
-
TOMTOM_KEY,
|
128 |
-
departure_time
|
129 |
-
))
|
130 |
-
|
131 |
-
# Parse JSON from the response
|
132 |
-
data = r.json()
|
133 |
-
#print(data)
|
134 |
-
|
135 |
-
#print(data)
|
136 |
-
|
137 |
-
result = data['routes'][0]['summary']
|
138 |
-
|
139 |
-
# Calculate distance in kilometers (1 meter = 0.001 kilometers)
|
140 |
-
distance_km = result['lengthInMeters'] * 0.001
|
141 |
-
|
142 |
-
# Calculate travel time in minutes (1 second = 1/60 minutes)
|
143 |
-
time_minutes = result['travelTimeInSeconds'] / 60
|
144 |
-
if time_minutes < 60:
|
145 |
-
time_display = f"{time_minutes:.0f} minutes"
|
146 |
-
else:
|
147 |
-
hours = int(time_minutes / 60)
|
148 |
-
minutes = int(time_minutes % 60)
|
149 |
-
time_display = f"{hours} hours" + (f" and {minutes} minutes" if minutes > 0 else "")
|
150 |
-
|
151 |
-
# Extract arrival time from the JSON structure
|
152 |
-
arrival_time_str = result['arrivalTime']
|
153 |
-
|
154 |
-
# Convert string to datetime object
|
155 |
-
arrival_time = datetime.fromisoformat(arrival_time_str)
|
156 |
-
|
157 |
-
# Extract and display the arrival hour in HH:MM format
|
158 |
-
arrival_hour_display = arrival_time.strftime("%H:%M")
|
159 |
-
|
160 |
-
|
161 |
-
# return the distance and time
|
162 |
-
return(f"The route to go to {address_destination} is {distance_km:.2f} km and {time_display}. Leaving now, the arrival time is estimated at {arrival_hour_display} " )
|
163 |
-
|
164 |
-
|
165 |
-
# Sort the results based on distance
|
166 |
-
#sorted_results = sorted(results, key=lambda x: x['dist'])
|
167 |
-
|
168 |
-
#return ". ".join(formatted_results)
|
169 |
-
|
170 |
-
|
171 |
-
def search_along_route(latitude_depart, longitude_depart, city_destination, type_of_poi):
|
172 |
-
"""
|
173 |
-
Return some of the closest points of interest along the route from the depart point, specified by its coordinates and a city destination.
|
174 |
-
:param latitude_depart (string): Required. Latitude of depart location
|
175 |
-
:param longitude_depart (string): Required. Longitude of depart location
|
176 |
-
:param city_destination (string): Required. City destination
|
177 |
-
:param type_of_poi (string): Required. type of point of interest depending on what the user wants to do.
|
178 |
-
"""
|
179 |
-
|
180 |
-
lat_dest, lon_dest = find_coordinates(city_destination)
|
181 |
-
print(lat_dest)
|
182 |
-
|
183 |
-
r = requests.get('https://api.tomtom.com/routing/1/calculateRoute/{0},{1}:{2},{3}/json?key={4}'.format(
|
184 |
-
latitude_depart,
|
185 |
-
longitude_depart,
|
186 |
-
lat_dest,
|
187 |
-
lon_dest,
|
188 |
-
TOMTOM_KEY
|
189 |
-
))
|
190 |
-
|
191 |
-
coord_route = select_equally_spaced_coordinates(r.json()['routes'][0]['legs'][0]['points'])
|
192 |
-
|
193 |
-
# The API endpoint for searching along a route
|
194 |
-
url = f'https://api.tomtom.com/search/2/searchAlongRoute/{type_of_poi}.json?key={TOMTOM_KEY}&maxDetourTime=700&limit=20&sortBy=detourTime'
|
195 |
-
|
196 |
-
# The data payload
|
197 |
-
payload = {
|
198 |
-
"route": {
|
199 |
-
"points": [
|
200 |
-
{"lat": float(latitude_depart), "lon": float(longitude_depart)},
|
201 |
-
{"lat": float(coord_route[1]['latitude']), "lon": float(coord_route[1]['longitude'])},
|
202 |
-
{"lat": float(coord_route[2]['latitude']), "lon": float(coord_route[2]['longitude'])},
|
203 |
-
{"lat": float(coord_route[3]['latitude']), "lon": float(coord_route[3]['longitude'])},
|
204 |
-
{"lat": float(coord_route[4]['latitude']), "lon": float(coord_route[4]['longitude'])},
|
205 |
-
{"lat": float(coord_route[5]['latitude']), "lon": float(coord_route[5]['longitude'])},
|
206 |
-
{"lat": float(coord_route[6]['latitude']), "lon": float(coord_route[6]['longitude'])},
|
207 |
-
{"lat": float(coord_route[7]['latitude']), "lon": float(coord_route[7]['longitude'])},
|
208 |
-
{"lat": float(coord_route[8]['latitude']), "lon": float(coord_route[8]['longitude'])},
|
209 |
-
{"lat": float(lat_dest), "lon": float(lon_dest)},
|
210 |
-
]
|
211 |
-
}
|
212 |
-
}
|
213 |
-
|
214 |
-
# Make the POST request
|
215 |
-
response = requests.post(url, json=payload)
|
216 |
-
|
217 |
-
# Check if the request was successful
|
218 |
-
if response.status_code == 200:
|
219 |
-
# Parse the JSON response
|
220 |
-
data = response.json()
|
221 |
-
print(json.dumps(data, indent=4))
|
222 |
-
else:
|
223 |
-
print('Failed to retrieve data:', response.status_code)
|
224 |
-
answer = ""
|
225 |
-
for result in data['results']:
|
226 |
-
name = result['poi']['name']
|
227 |
-
address = result['address']['freeformAddress']
|
228 |
-
detour_time = result['detourTime']
|
229 |
-
answer = answer + f" \nAlong the route to {city_destination}, there is the {name} at {address} that would represent a detour of {int(detour_time/60)} minutes."
|
230 |
-
|
231 |
-
return answer
|
232 |
-
|
233 |
-
|
234 |
-
#current weather API
|
235 |
-
def get_weather(city_name:str= "", **kwargs):
|
236 |
-
"""
|
237 |
-
Returns the CURRENT weather in a specified city.
|
238 |
-
Args:
|
239 |
-
city_name (string) : Required. The name of the city.
|
240 |
-
"""
|
241 |
-
# The endpoint URL provided by WeatherAPI
|
242 |
-
url = f"http://api.weatherapi.com/v1/current.json?key={WEATHER_API_KEY}&q={city_name}&aqi=no"
|
243 |
-
|
244 |
-
# Make the API request
|
245 |
-
response = requests.get(url)
|
246 |
-
|
247 |
-
if response.status_code == 200:
|
248 |
-
# Parse the JSON response
|
249 |
-
weather_data = response.json()
|
250 |
-
|
251 |
-
# Extracting the necessary pieces of data
|
252 |
-
location = weather_data['location']['name']
|
253 |
-
region = weather_data['location']['region']
|
254 |
-
country = weather_data['location']['country']
|
255 |
-
time = weather_data['location']['localtime']
|
256 |
-
temperature_c = weather_data['current']['temp_c']
|
257 |
-
condition_text = weather_data['current']['condition']['text']
|
258 |
-
wind_mph = weather_data['current']['wind_mph']
|
259 |
-
humidity = weather_data['current']['humidity']
|
260 |
-
feelslike_c = weather_data['current']['feelslike_c']
|
261 |
-
|
262 |
-
# Formulate the sentences
|
263 |
-
weather_sentences = (
|
264 |
-
f"The current weather in {location}, {region}, {country} is {condition_text} "
|
265 |
-
f"with a temperature of {temperature_c}°C that feels like {feelslike_c}°C. "
|
266 |
-
f"Humidity is at {humidity}%. "
|
267 |
-
f"Wind speed is {wind_mph} mph."
|
268 |
-
)
|
269 |
-
return weather_sentences
|
270 |
-
else:
|
271 |
-
# Handle errors
|
272 |
-
return f"Failed to get weather data: {response.status_code}, {response.text}"
|
273 |
-
|
274 |
-
|
275 |
-
|
276 |
-
#weather forecast API
|
277 |
-
def get_forecast(city_name:str= "", when = 0, **kwargs):
|
278 |
-
"""
|
279 |
-
Returns the weather forecast in a specified number of days for a specified city .
|
280 |
-
Args:
|
281 |
-
city_name (string) : Required. The name of the city.
|
282 |
-
when (int) : Required. in number of days (until the day for which we want to know the forecast) (example: tomorrow is 1, in two days is 2, etc.)
|
283 |
-
"""
|
284 |
-
#print(when)
|
285 |
-
when +=1
|
286 |
-
# The endpoint URL provided by WeatherAPI
|
287 |
-
url = f"http://api.weatherapi.com/v1/forecast.json?key={WEATHER_API_KEY}&q={city_name}&days={str(when)}&aqi=no"
|
288 |
-
|
289 |
-
|
290 |
-
# Make the API request
|
291 |
-
response = requests.get(url)
|
292 |
-
|
293 |
-
if response.status_code == 200:
|
294 |
-
# Parse the JSON response
|
295 |
-
data = response.json()
|
296 |
-
|
297 |
-
# Initialize an empty string to hold our result
|
298 |
-
forecast_sentences = ""
|
299 |
-
|
300 |
-
# Extract city information
|
301 |
-
location = data.get('location', {})
|
302 |
-
city_name = location.get('name', 'the specified location')
|
303 |
-
|
304 |
-
#print(data)
|
305 |
-
|
306 |
-
|
307 |
-
# Extract the forecast days
|
308 |
-
forecast_days = data.get('forecast', {}).get('forecastday', [])[when-1:]
|
309 |
-
#number = 0
|
310 |
-
|
311 |
-
#print (forecast_days)
|
312 |
-
|
313 |
-
for day in forecast_days:
|
314 |
-
date = day.get('date', 'a specific day')
|
315 |
-
conditions = day.get('day', {}).get('condition', {}).get('text', 'weather conditions')
|
316 |
-
max_temp_c = day.get('day', {}).get('maxtemp_c', 'N/A')
|
317 |
-
min_temp_c = day.get('day', {}).get('mintemp_c', 'N/A')
|
318 |
-
chance_of_rain = day.get('day', {}).get('daily_chance_of_rain', 'N/A')
|
319 |
-
|
320 |
-
if when == 1:
|
321 |
-
number_str = 'today'
|
322 |
-
elif when == 2:
|
323 |
-
number_str = 'tomorrow'
|
324 |
-
else:
|
325 |
-
number_str = f'in {when-1} days'
|
326 |
-
|
327 |
-
# Generate a sentence for the day's forecast
|
328 |
-
forecast_sentence = f"On {date} ({number_str}) in {city_name}, the weather will be {conditions} with a high of {max_temp_c}°C and a low of {min_temp_c}°C. There's a {chance_of_rain}% chance of rain. "
|
329 |
-
|
330 |
-
#number = number + 1
|
331 |
-
# Add the sentence to the result
|
332 |
-
forecast_sentences += forecast_sentence
|
333 |
-
return forecast_sentences
|
334 |
-
else:
|
335 |
-
# Handle errors
|
336 |
-
print( f"Failed to get weather data: {response.status_code}, {response.text}")
|
337 |
-
return f'error {response.status_code}'
|
338 |
-
|
339 |
|
|
|
|
|
|
|
1 |
from geopy.geocoders import Nominatim
|
2 |
|
3 |
|
|
|
51 |
lon = coord.longitude
|
52 |
return lat, lon, city
|
53 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
54 |
|
car_assistant_eta.ipynb
ADDED
The diff for this file is too large to render.
See raw diff
|
|
car_assistant_slim.ipynb
CHANGED
@@ -19,13 +19,11 @@
|
|
19 |
"name": "stderr",
|
20 |
"output_type": "stream",
|
21 |
"text": [
|
22 |
-
"/opt/
|
23 |
-
" from .autonotebook import tqdm as notebook_tqdm\n",
|
24 |
-
"/opt/conda/lib/python3.10/site-packages/transformers/utils/generic.py:441: UserWarning: torch.utils._pytree._register_pytree_node is deprecated. Please use torch.utils._pytree.register_pytree_node instead.\n",
|
25 |
" _torch_pytree._register_pytree_node(\n",
|
26 |
-
"/opt/
|
27 |
" _torch_pytree._register_pytree_node(\n",
|
28 |
-
"/opt/
|
29 |
" _torch_pytree._register_pytree_node(\n"
|
30 |
]
|
31 |
}
|
@@ -116,21 +114,35 @@
|
|
116 |
"name": "stderr",
|
117 |
"output_type": "stream",
|
118 |
"text": [
|
119 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
120 |
]
|
121 |
},
|
122 |
{
|
123 |
"name": "stdout",
|
124 |
"output_type": "stream",
|
125 |
"text": [
|
126 |
-
" > tts_models
|
127 |
]
|
128 |
},
|
129 |
{
|
130 |
"name": "stderr",
|
131 |
"output_type": "stream",
|
132 |
"text": [
|
133 |
-
"
|
|
|
|
|
|
|
134 |
" _torch_pytree._register_pytree_node(\n"
|
135 |
]
|
136 |
},
|
@@ -138,6 +150,8 @@
|
|
138 |
"name": "stdout",
|
139 |
"output_type": "stream",
|
140 |
"text": [
|
|
|
|
|
141 |
" > Using model: xtts\n"
|
142 |
]
|
143 |
}
|
@@ -160,7 +174,20 @@
|
|
160 |
"collapsed": true,
|
161 |
"id": "JNALTDb0LT90"
|
162 |
},
|
163 |
-
"outputs": [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
164 |
"source": [
|
165 |
"#load model language recognition\n",
|
166 |
"model_ckpt = \"papluca/xlm-roberta-base-language-detection\"\n",
|
@@ -183,8 +210,10 @@
|
|
183 |
"source": [
|
184 |
"#load model llama2\n",
|
185 |
"mn = 'stabilityai/StableBeluga-7B' #mn = \"TheBloke/Llama-2-7b-Chat-GPTQ\" --> other possibility \n",
|
186 |
-
"model = AutoModelForCausalLM.from_pretrained(mn, device_map=0, load_in_4bit=True) #torch_dtype=torch.float16\n",
|
187 |
-
"
|
|
|
|
|
188 |
]
|
189 |
},
|
190 |
{
|
@@ -881,7 +910,7 @@
|
|
881 |
"name": "python",
|
882 |
"nbconvert_exporter": "python",
|
883 |
"pygments_lexer": "ipython3",
|
884 |
-
"version": "3.
|
885 |
}
|
886 |
},
|
887 |
"nbformat": 4,
|
|
|
19 |
"name": "stderr",
|
20 |
"output_type": "stream",
|
21 |
"text": [
|
22 |
+
"/opt/homebrew/Caskroom/miniconda/base/envs/llm/lib/python3.11/site-packages/transformers/utils/generic.py:441: UserWarning: torch.utils._pytree._register_pytree_node is deprecated. Please use torch.utils._pytree.register_pytree_node instead.\n",
|
|
|
|
|
23 |
" _torch_pytree._register_pytree_node(\n",
|
24 |
+
"/opt/homebrew/Caskroom/miniconda/base/envs/llm/lib/python3.11/site-packages/transformers/utils/generic.py:309: UserWarning: torch.utils._pytree._register_pytree_node is deprecated. Please use torch.utils._pytree.register_pytree_node instead.\n",
|
25 |
" _torch_pytree._register_pytree_node(\n",
|
26 |
+
"/opt/homebrew/Caskroom/miniconda/base/envs/llm/lib/python3.11/site-packages/transformers/utils/generic.py:309: UserWarning: torch.utils._pytree._register_pytree_node is deprecated. Please use torch.utils._pytree.register_pytree_node instead.\n",
|
27 |
" _torch_pytree._register_pytree_node(\n"
|
28 |
]
|
29 |
}
|
|
|
114 |
"name": "stderr",
|
115 |
"output_type": "stream",
|
116 |
"text": [
|
117 |
+
"preprocessor_config.json: 100%|██████████| 185k/185k [00:00<00:00, 94.3MB/s]\n",
|
118 |
+
"tokenizer_config.json: 100%|██████████| 283k/283k [00:00<00:00, 1.05MB/s]\n",
|
119 |
+
"vocab.json: 100%|██████████| 836k/836k [00:00<00:00, 3.03MB/s]\n",
|
120 |
+
"tokenizer.json: 100%|██████████| 2.48M/2.48M [00:00<00:00, 50.6MB/s]\n",
|
121 |
+
"merges.txt: 100%|██████████| 494k/494k [00:00<00:00, 28.8MB/s]\n",
|
122 |
+
"normalizer.json: 100%|██████████| 52.7k/52.7k [00:00<00:00, 67.8MB/s]\n",
|
123 |
+
"added_tokens.json: 100%|██████████| 34.6k/34.6k [00:00<00:00, 38.7MB/s]\n",
|
124 |
+
"special_tokens_map.json: 100%|██████████| 2.19k/2.19k [00:00<00:00, 8.88MB/s]\n",
|
125 |
+
"Special tokens have been added in the vocabulary, make sure the associated word embeddings are fine-tuned or trained.\n",
|
126 |
+
"config.json: 100%|██████████| 1.97k/1.97k [00:00<00:00, 4.46MB/s]\n",
|
127 |
+
"model.safetensors: 100%|██████████| 967M/967M [00:12<00:00, 74.9MB/s] \n",
|
128 |
+
"generation_config.json: 100%|██████████| 3.87k/3.87k [00:00<00:00, 39.0MB/s]\n"
|
129 |
]
|
130 |
},
|
131 |
{
|
132 |
"name": "stdout",
|
133 |
"output_type": "stream",
|
134 |
"text": [
|
135 |
+
" > Downloading model to /Users/sasan.jafarnejad/Library/Application Support/tts/tts_models--multilingual--multi-dataset--xtts_v1.1\n"
|
136 |
]
|
137 |
},
|
138 |
{
|
139 |
"name": "stderr",
|
140 |
"output_type": "stream",
|
141 |
"text": [
|
142 |
+
"100%|██████████| 1.87G/1.87G [00:24<00:00, 75.6MiB/s]\n",
|
143 |
+
"100%|██████████| 4.70k/4.70k [00:00<00:00, 17.9kiB/s]\n",
|
144 |
+
"100%|██████████| 294k/294k [00:00<00:00, 1.23MiB/s]\n",
|
145 |
+
"/opt/homebrew/Caskroom/miniconda/base/envs/llm/lib/python3.11/site-packages/transformers/utils/generic.py:309: UserWarning: torch.utils._pytree._register_pytree_node is deprecated. Please use torch.utils._pytree.register_pytree_node instead.\n",
|
146 |
" _torch_pytree._register_pytree_node(\n"
|
147 |
]
|
148 |
},
|
|
|
150 |
"name": "stdout",
|
151 |
"output_type": "stream",
|
152 |
"text": [
|
153 |
+
" > Model's license - CPML\n",
|
154 |
+
" > Check https://coqui.ai/cpml.txt for more info.\n",
|
155 |
" > Using model: xtts\n"
|
156 |
]
|
157 |
}
|
|
|
174 |
"collapsed": true,
|
175 |
"id": "JNALTDb0LT90"
|
176 |
},
|
177 |
+
"outputs": [
|
178 |
+
{
|
179 |
+
"name": "stderr",
|
180 |
+
"output_type": "stream",
|
181 |
+
"text": [
|
182 |
+
"config.json: 100%|█████████��| 1.42k/1.42k [00:00<00:00, 3.24MB/s]\n",
|
183 |
+
"model.safetensors: 100%|██████████| 1.11G/1.11G [00:13<00:00, 79.5MB/s]\n",
|
184 |
+
"tokenizer_config.json: 100%|██████████| 502/502 [00:00<00:00, 5.01MB/s]\n",
|
185 |
+
"sentencepiece.bpe.model: 100%|██████████| 5.07M/5.07M [00:00<00:00, 78.4MB/s]\n",
|
186 |
+
"tokenizer.json: 100%|██████████| 9.08M/9.08M [00:00<00:00, 61.5MB/s]\n",
|
187 |
+
"special_tokens_map.json: 100%|██████████| 239/239 [00:00<00:00, 372kB/s]\n"
|
188 |
+
]
|
189 |
+
}
|
190 |
+
],
|
191 |
"source": [
|
192 |
"#load model language recognition\n",
|
193 |
"model_ckpt = \"papluca/xlm-roberta-base-language-detection\"\n",
|
|
|
210 |
"source": [
|
211 |
"#load model llama2\n",
|
212 |
"mn = 'stabilityai/StableBeluga-7B' #mn = \"TheBloke/Llama-2-7b-Chat-GPTQ\" --> other possibility \n",
|
213 |
+
"# model = AutoModelForCausalLM.from_pretrained(mn, device_map=0, load_in_4bit=True) #torch_dtype=torch.float16\n",
|
214 |
+
"model = AutoModelForCausalLM.from_pretrained(mn, device_map=0) #torch_dtype=torch.float16\n",
|
215 |
+
"# tokr = AutoTokenizer.from_pretrained(mn, load_in_4bit=True) #tokenizer\n",
|
216 |
+
"tokr = AutoTokenizer.from_pretrained(mn) #tokenizer"
|
217 |
]
|
218 |
},
|
219 |
{
|
|
|
910 |
"name": "python",
|
911 |
"nbconvert_exporter": "python",
|
912 |
"pygments_lexer": "ipython3",
|
913 |
+
"version": "3.11.8"
|
914 |
}
|
915 |
},
|
916 |
"nbformat": 4,
|
car_assistant_text.ipynb
ADDED
The diff for this file is too large to render.
See raw diff
|
|
core/__init__.py
ADDED
@@ -0,0 +1,199 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
from collections import namedtuple
|
3 |
+
import time
|
4 |
+
import pathlib
|
5 |
+
from typing import List
|
6 |
+
|
7 |
+
import numpy as np
|
8 |
+
import torch
|
9 |
+
from TTS.api import TTS
|
10 |
+
|
11 |
+
os.environ["COQUI_TOS_AGREED"] = "1"
|
12 |
+
|
13 |
+
|
14 |
+
Voice = namedtuple("voice", ["name", "neutral", "angry", "speed"])
|
15 |
+
|
16 |
+
file_full_path = pathlib.Path(os.path.realpath(__file__)).parent
|
17 |
+
|
18 |
+
voices = [
|
19 |
+
Voice(
|
20 |
+
"Attenborough",
|
21 |
+
neutral=f"{file_full_path}/audio/attenborough/neutral.wav",
|
22 |
+
angry=None,
|
23 |
+
speed=1.1,
|
24 |
+
),
|
25 |
+
Voice(
|
26 |
+
"Rick",
|
27 |
+
neutral=f"{file_full_path}/audio/rick/neutral.wav",
|
28 |
+
angry=None,
|
29 |
+
speed=1.1,
|
30 |
+
),
|
31 |
+
Voice(
|
32 |
+
"Freeman",
|
33 |
+
neutral=f"{file_full_path}/audio/freeman/neutral.wav",
|
34 |
+
angry="audio/freeman/angry.wav",
|
35 |
+
speed=1.1,
|
36 |
+
),
|
37 |
+
Voice(
|
38 |
+
"Walken",
|
39 |
+
neutral=f"{file_full_path}/audio/walken/neutral.wav",
|
40 |
+
angry=None,
|
41 |
+
speed=1.1,
|
42 |
+
),
|
43 |
+
Voice(
|
44 |
+
"Darth Wader",
|
45 |
+
neutral=f"{file_full_path}/audio/darth/neutral.wav",
|
46 |
+
angry=None,
|
47 |
+
speed=1.1,
|
48 |
+
),
|
49 |
+
]
|
50 |
+
|
51 |
+
|
52 |
+
def load_tts_pipeline():
|
53 |
+
# load model for text to speech
|
54 |
+
device = "cuda" if torch.cuda.is_available() else "cpu"
|
55 |
+
# device = "mps"
|
56 |
+
tts_pipeline = TTS("tts_models/multilingual/multi-dataset/xtts_v2").to(device)
|
57 |
+
return tts_pipeline
|
58 |
+
|
59 |
+
|
60 |
+
def compute_speaker_embedding(voice_path: str, config, pipeline, cache):
|
61 |
+
if voice_path not in cache:
|
62 |
+
cache[voice_path] = pipeline.synthesizer.tts_model.get_conditioning_latents(
|
63 |
+
audio_path=voice_path,
|
64 |
+
gpt_cond_len=config.gpt_cond_len,
|
65 |
+
gpt_cond_chunk_len=config.gpt_cond_chunk_len,
|
66 |
+
max_ref_length=config.max_ref_len,
|
67 |
+
sound_norm_refs=config.sound_norm_refs,
|
68 |
+
)
|
69 |
+
return cache[voice_path]
|
70 |
+
|
71 |
+
|
72 |
+
voice_options = []
|
73 |
+
for voice in voices:
|
74 |
+
if voice.neutral:
|
75 |
+
voice_options.append(f"{voice.name} - Neutral")
|
76 |
+
if voice.angry:
|
77 |
+
voice_options.append(f"{voice.name} - Angry")
|
78 |
+
|
79 |
+
|
80 |
+
def voice_from_text(voice):
|
81 |
+
for v in voices:
|
82 |
+
if voice == f"{v.name} - Neutral":
|
83 |
+
return v.neutral
|
84 |
+
if voice == f"{v.name} - Angry":
|
85 |
+
return v.angry
|
86 |
+
raise ValueError(f"Voice {voice} not found.")
|
87 |
+
|
88 |
+
|
89 |
+
def speed_from_text(voice):
|
90 |
+
for v in voices:
|
91 |
+
if voice == f"{v.name} - Neutral":
|
92 |
+
return v.speed
|
93 |
+
if voice == f"{v.name} - Angry":
|
94 |
+
return v.speed
|
95 |
+
|
96 |
+
|
97 |
+
def tts(
|
98 |
+
self,
|
99 |
+
text: str = "",
|
100 |
+
language_name: str = "",
|
101 |
+
reference_wav=None,
|
102 |
+
gpt_cond_latent=None,
|
103 |
+
speaker_embedding=None,
|
104 |
+
split_sentences: bool = True,
|
105 |
+
**kwargs,
|
106 |
+
) -> List[int]:
|
107 |
+
"""🐸 TTS magic. Run all the models and generate speech.
|
108 |
+
|
109 |
+
Args:
|
110 |
+
text (str): input text.
|
111 |
+
speaker_name (str, optional): speaker id for multi-speaker models. Defaults to "".
|
112 |
+
language_name (str, optional): language id for multi-language models. Defaults to "".
|
113 |
+
speaker_wav (Union[str, List[str]], optional): path to the speaker wav for voice cloning. Defaults to None.
|
114 |
+
style_wav ([type], optional): style waveform for GST. Defaults to None.
|
115 |
+
style_text ([type], optional): transcription of style_wav for Capacitron. Defaults to None.
|
116 |
+
reference_wav ([type], optional): reference waveform for voice conversion. Defaults to None.
|
117 |
+
reference_speaker_name ([type], optional): speaker id of reference waveform. Defaults to None.
|
118 |
+
split_sentences (bool, optional): split the input text into sentences. Defaults to True.
|
119 |
+
**kwargs: additional arguments to pass to the TTS model.
|
120 |
+
Returns:
|
121 |
+
List[int]: [description]
|
122 |
+
"""
|
123 |
+
start_time = time.time()
|
124 |
+
use_gl = self.vocoder_model is None
|
125 |
+
wavs = []
|
126 |
+
|
127 |
+
if not text and not reference_wav:
|
128 |
+
raise ValueError(
|
129 |
+
"You need to define either `text` (for sythesis) or a `reference_wav` (for voice conversion) to use the Coqui TTS API."
|
130 |
+
)
|
131 |
+
|
132 |
+
if text:
|
133 |
+
sens = [text]
|
134 |
+
if split_sentences:
|
135 |
+
print(" > Text splitted to sentences.")
|
136 |
+
sens = self.split_into_sentences(text)
|
137 |
+
print(sens)
|
138 |
+
|
139 |
+
if not reference_wav: # not voice conversion
|
140 |
+
for sen in sens:
|
141 |
+
outputs = self.tts_model.inference(
|
142 |
+
sen,
|
143 |
+
language_name,
|
144 |
+
gpt_cond_latent,
|
145 |
+
speaker_embedding,
|
146 |
+
# GPT inference
|
147 |
+
temperature=0.75,
|
148 |
+
length_penalty=1.0,
|
149 |
+
repetition_penalty=10.0,
|
150 |
+
top_k=50,
|
151 |
+
top_p=0.85,
|
152 |
+
do_sample=True,
|
153 |
+
**kwargs,
|
154 |
+
)
|
155 |
+
waveform = outputs["wav"]
|
156 |
+
if (
|
157 |
+
torch.is_tensor(waveform)
|
158 |
+
and waveform.device != torch.device("cpu")
|
159 |
+
and not use_gl
|
160 |
+
):
|
161 |
+
waveform = waveform.cpu()
|
162 |
+
if not use_gl:
|
163 |
+
waveform = waveform.numpy()
|
164 |
+
waveform = waveform.squeeze()
|
165 |
+
|
166 |
+
# # trim silence
|
167 |
+
# if (
|
168 |
+
# "do_trim_silence" in self.tts_config.audio
|
169 |
+
# and self.tts_config.audio["do_trim_silence"]
|
170 |
+
# ):
|
171 |
+
# waveform = trim_silence(waveform, self.tts_model.ap)
|
172 |
+
|
173 |
+
wavs += list(waveform)
|
174 |
+
wavs += [0] * 10000
|
175 |
+
|
176 |
+
# compute stats
|
177 |
+
process_time = time.time() - start_time
|
178 |
+
audio_time = len(wavs) / self.tts_config.audio["sample_rate"]
|
179 |
+
print(f" > Processing time: {process_time}")
|
180 |
+
print(f" > Real-time factor: {process_time / audio_time}")
|
181 |
+
return wavs
|
182 |
+
|
183 |
+
|
184 |
+
def tts_gradio(tts_pipeline, text, voice, cache):
|
185 |
+
voice_path = voice_from_text(voice)
|
186 |
+
(gpt_cond_latent, speaker_embedding) = compute_speaker_embedding(
|
187 |
+
voice_path, tts_pipeline.synthesizer.tts_config, tts_pipeline, cache
|
188 |
+
)
|
189 |
+
out = tts(
|
190 |
+
tts_pipeline.synthesizer,
|
191 |
+
text,
|
192 |
+
language_name="en",
|
193 |
+
speaker=None,
|
194 |
+
gpt_cond_latent=gpt_cond_latent,
|
195 |
+
speaker_embedding=speaker_embedding,
|
196 |
+
speed=1.1,
|
197 |
+
# file_path="out.wav",
|
198 |
+
)
|
199 |
+
return (22050, np.array(out)), dict(text=text, voice=voice)
|
kitt.py
ADDED
@@ -0,0 +1,331 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import time
|
2 |
+
import gradio as gr
|
3 |
+
import numpy as np
|
4 |
+
import requests
|
5 |
+
import torch
|
6 |
+
import torchaudio
|
7 |
+
from transformers import pipeline
|
8 |
+
|
9 |
+
|
10 |
+
|
11 |
+
import skills
|
12 |
+
from skills.common import config, vehicle
|
13 |
+
from skills.routing import calculate_route
|
14 |
+
import ollama
|
15 |
+
|
16 |
+
### LLM Stuff ###
|
17 |
+
from langchain_community.llms import Ollama
|
18 |
+
from langchain.tools.base import StructuredTool
|
19 |
+
|
20 |
+
from skills import (
|
21 |
+
get_weather,
|
22 |
+
find_route,
|
23 |
+
get_forecast,
|
24 |
+
vehicle_status as vehicle_status_fn,
|
25 |
+
search_points_of_interests,
|
26 |
+
search_along_route_w_coordinates,
|
27 |
+
do_anything_else,
|
28 |
+
date_time_info
|
29 |
+
)
|
30 |
+
from skills import extract_func_args
|
31 |
+
from core import voice_options, load_tts_pipeline, tts_gradio
|
32 |
+
|
33 |
+
|
34 |
+
global_context = {
|
35 |
+
"vehicle": vehicle,
|
36 |
+
"query": "How is the weather?",
|
37 |
+
"route_points": [],
|
38 |
+
}
|
39 |
+
|
40 |
+
speaker_embedding_cache = {}
|
41 |
+
|
42 |
+
MODEL_FUNC = "nexusraven"
|
43 |
+
MODEL_GENERAL = "llama3:instruct"
|
44 |
+
|
45 |
+
RAVEN_PROMPT_FUNC = """You are a helpful AI assistant in a car (vehicle), that follows instructions extremely well. \
|
46 |
+
Answer questions concisely and do not mention what you base your reply on."
|
47 |
+
|
48 |
+
{raven_tools}
|
49 |
+
|
50 |
+
{history}
|
51 |
+
|
52 |
+
User Query: Question: {input}<human_end>
|
53 |
+
"""
|
54 |
+
|
55 |
+
def get_prompt(template, input, history, tools):
|
56 |
+
# "vehicle_status": vehicle_status_fn()[0]
|
57 |
+
kwargs = {"history": history, "input": input}
|
58 |
+
prompt = "<human>:\n"
|
59 |
+
for tool in tools:
|
60 |
+
func_signature, func_docstring = tool.description.split(" - ", 1)
|
61 |
+
prompt += f'Function:\n<func_start>def {func_signature}<func_end>\n<docstring_start>\n"""\n{func_docstring}\n"""\n<docstring_end>\n'
|
62 |
+
kwargs["raven_tools"] = prompt
|
63 |
+
|
64 |
+
if history:
|
65 |
+
kwargs["history"] = f"Previous conversation history:{history}\n"
|
66 |
+
|
67 |
+
return template.format(**kwargs).replace("{{", "{").replace("}}", "}")
|
68 |
+
|
69 |
+
def use_tool(func_name, kwargs, tools):
|
70 |
+
for tool in tools:
|
71 |
+
if tool.name == func_name:
|
72 |
+
return tool.invoke(input=kwargs)
|
73 |
+
return None
|
74 |
+
|
75 |
+
# llm = Ollama(model="nexusraven", stop=["\nReflection:", "\nThought:"], keep_alive=60*10)
|
76 |
+
|
77 |
+
|
78 |
+
# Generate options for hours (00-23)
|
79 |
+
hour_options = [f"{i:02d}:00:00" for i in range(24)]
|
80 |
+
|
81 |
+
|
82 |
+
def search_along_route(query=""):
|
83 |
+
"""Search for points of interest along the route/way to the destination.
|
84 |
+
|
85 |
+
Args:
|
86 |
+
query (str, optional): The type of point of interest to search for. Defaults to "restaurant".
|
87 |
+
|
88 |
+
"""
|
89 |
+
points = global_context["route_points"]
|
90 |
+
# maybe reshape
|
91 |
+
return search_along_route_w_coordinates(points, query)
|
92 |
+
|
93 |
+
|
94 |
+
def set_time(time_picker):
|
95 |
+
vehicle.time = time_picker
|
96 |
+
return vehicle.model_dump_json()
|
97 |
+
|
98 |
+
|
99 |
+
def get_vehicle_status(state):
|
100 |
+
return state.value["vehicle"].model_dump_json()
|
101 |
+
|
102 |
+
|
103 |
+
tools = [
|
104 |
+
StructuredTool.from_function(get_weather),
|
105 |
+
StructuredTool.from_function(find_route),
|
106 |
+
# StructuredTool.from_function(vehicle_status),
|
107 |
+
StructuredTool.from_function(search_points_of_interests),
|
108 |
+
StructuredTool.from_function(search_along_route),
|
109 |
+
StructuredTool.from_function(date_time_info),
|
110 |
+
StructuredTool.from_function(do_anything_else),
|
111 |
+
]
|
112 |
+
|
113 |
+
|
114 |
+
def run_generic_model(query):
|
115 |
+
print(f"Running the generic model with query: {query}")
|
116 |
+
data = {
|
117 |
+
"prompt": f"Answer the question below in a short and concise manner.\n{query}",
|
118 |
+
"model": MODEL_GENERAL,
|
119 |
+
"options": {
|
120 |
+
# "temperature": 0.1,
|
121 |
+
# "stop":["\nReflection:", "\nThought:"]
|
122 |
+
}
|
123 |
+
}
|
124 |
+
out = ollama.generate(**data)
|
125 |
+
return out["response"]
|
126 |
+
|
127 |
+
|
128 |
+
def run_model(query, voice_character):
|
129 |
+
query = query.strip().replace("'", "")
|
130 |
+
print("Query: ", query)
|
131 |
+
global_context["query"] = query
|
132 |
+
global_context["prompt"] = get_prompt(RAVEN_PROMPT_FUNC, query, "", tools)
|
133 |
+
print("Prompt: ", global_context["prompt"])
|
134 |
+
data = {
|
135 |
+
"prompt": global_context["prompt"],
|
136 |
+
# "streaming": False,
|
137 |
+
"model": "nexusraven",
|
138 |
+
# "model": "smangrul/llama-3-8b-instruct-function-calling",
|
139 |
+
"raw": True,
|
140 |
+
"options": {
|
141 |
+
"temperature": 0.5,
|
142 |
+
"stop":["\nReflection:", "\nThought:"]
|
143 |
+
}
|
144 |
+
}
|
145 |
+
out = ollama.generate(**data)
|
146 |
+
llm_response = out["response"]
|
147 |
+
if "Call: " in llm_response:
|
148 |
+
print(f"llm_response: {llm_response}")
|
149 |
+
llm_response = llm_response.replace("<bot_end>"," ")
|
150 |
+
func_name, kwargs = extract_func_args(llm_response)
|
151 |
+
print(f"Function: {func_name}, Args: {kwargs}")
|
152 |
+
if func_name == "do_anything_else":
|
153 |
+
output_text = run_generic_model(query)
|
154 |
+
else:
|
155 |
+
output_text = use_tool(func_name, kwargs, tools)
|
156 |
+
else:
|
157 |
+
output_text = out["response"]
|
158 |
+
|
159 |
+
if type(output_text) == tuple:
|
160 |
+
output_text = output_text[0]
|
161 |
+
gr.Info(f"Output text: {output_text}, generating voice output...")
|
162 |
+
return output_text, tts_gradio(tts_pipeline, output_text, voice_character, speaker_embedding_cache)[0]
|
163 |
+
|
164 |
+
|
165 |
+
def calculate_route_gradio(origin, destination):
|
166 |
+
plot, vehicle_status, points = calculate_route(origin, destination)
|
167 |
+
global_context["route_points"] = points
|
168 |
+
vehicle.location_coordinates = points[0]["latitude"], points[0]["longitude"]
|
169 |
+
return plot, vehicle_status
|
170 |
+
|
171 |
+
|
172 |
+
def update_vehicle_status(trip_progress):
|
173 |
+
n_points = len(global_context["route_points"])
|
174 |
+
new_coords = global_context["route_points"][min(int(trip_progress / 100 * n_points), n_points - 1)]
|
175 |
+
new_coords = new_coords["latitude"], new_coords["longitude"]
|
176 |
+
print(f"Trip progress: {trip_progress}, len: {n_points}, new_coords: {new_coords}")
|
177 |
+
vehicle.location_coordinates = new_coords
|
178 |
+
vehicle.location = ""
|
179 |
+
return vehicle.model_dump_json()
|
180 |
+
|
181 |
+
|
182 |
+
device = "cuda" if torch.cuda.is_available() else "cpu"
|
183 |
+
transcriber = pipeline("automatic-speech-recognition", model="openai/whisper-base.en", device=device)
|
184 |
+
|
185 |
+
|
186 |
+
def save_audio_as_wav(data, sample_rate, file_path):
|
187 |
+
# make a tensor from the numpy array
|
188 |
+
data = torch.tensor(data).reshape(1, -1)
|
189 |
+
torchaudio.save(file_path, data, sample_rate=sample_rate, bits_per_sample=16, encoding="PCM_S")
|
190 |
+
|
191 |
+
|
192 |
+
def save_and_transcribe_audio(audio):
|
193 |
+
try:
|
194 |
+
# capture the audio and save it to a file as wav or mp3
|
195 |
+
# file_name = save("audioinput.wav")
|
196 |
+
sr, y = audio
|
197 |
+
# y = y.astype(np.float32)
|
198 |
+
# y /= np.max(np.abs(y))
|
199 |
+
|
200 |
+
# add timestamp to file name
|
201 |
+
filename = f"recordings/audio{time.time()}.wav"
|
202 |
+
save_audio_as_wav(y, sr, filename)
|
203 |
+
|
204 |
+
sr, y = audio
|
205 |
+
y = y.astype(np.float32)
|
206 |
+
y /= np.max(np.abs(y))
|
207 |
+
text = transcriber({"sampling_rate": sr, "raw":y})["text"]
|
208 |
+
except Exception as e:
|
209 |
+
print(f"Error: {e}")
|
210 |
+
return "Error transcribing audio"
|
211 |
+
return text
|
212 |
+
|
213 |
+
# to be able to use the microphone on chrome, you will have to go to chrome://flags/#unsafely-treat-insecure-origin-as-secure and enter http://10.186.115.21:7860/
|
214 |
+
# in "Insecure origins treated as secure", enable it and relaunch chrome
|
215 |
+
|
216 |
+
# example question:
|
217 |
+
# what's the weather like outside?
|
218 |
+
# What's the closest restaurant from here?
|
219 |
+
|
220 |
+
|
221 |
+
tts_pipeline = load_tts_pipeline()
|
222 |
+
|
223 |
+
|
224 |
+
with gr.Blocks(theme=gr.themes.Default()) as demo:
|
225 |
+
state = gr.State(
|
226 |
+
value={
|
227 |
+
# "context": initial_context,
|
228 |
+
"query": "",
|
229 |
+
"route_points": [],
|
230 |
+
}
|
231 |
+
)
|
232 |
+
trip_points = gr.State(value=[])
|
233 |
+
|
234 |
+
with gr.Row():
|
235 |
+
with gr.Column(scale=1, min_width=300):
|
236 |
+
time_picker = gr.Dropdown(
|
237 |
+
choices=hour_options,
|
238 |
+
label="What time is it? (HH:MM)",
|
239 |
+
value="08:00:00",
|
240 |
+
interactive=True,
|
241 |
+
)
|
242 |
+
history = gr.Radio(
|
243 |
+
["Yes", "No"],
|
244 |
+
label="Maintain the conversation history?",
|
245 |
+
value="No",
|
246 |
+
interactive=True,
|
247 |
+
)
|
248 |
+
voice_character = gr.Radio(choices=voice_options, label='Choose a voice', value=voice_options[0], show_label=True)
|
249 |
+
origin = gr.Textbox(
|
250 |
+
value="Mondorf-les-Bains, Luxembourg", label="Origin", interactive=True
|
251 |
+
)
|
252 |
+
destination = gr.Textbox(
|
253 |
+
value="Rue Alphonse Weicker, Luxembourg",
|
254 |
+
label="Destination",
|
255 |
+
interactive=True,
|
256 |
+
)
|
257 |
+
|
258 |
+
with gr.Column(scale=2, min_width=600):
|
259 |
+
map_plot = gr.Plot()
|
260 |
+
trip_progress = gr.Slider(0, 100, step=5, label="Trip progress", interactive=True)
|
261 |
+
|
262 |
+
# map_if = gr.Interface(fn=plot_map, inputs=year_input, outputs=map_plot)
|
263 |
+
|
264 |
+
with gr.Row():
|
265 |
+
with gr.Column():
|
266 |
+
input_audio = gr.Audio(
|
267 |
+
type="numpy",sources=["microphone"], label="Input audio", elem_id="input_audio"
|
268 |
+
)
|
269 |
+
input_text = gr.Textbox(
|
270 |
+
value="How is the weather?", label="Input text", interactive=True
|
271 |
+
)
|
272 |
+
vehicle_status = gr.JSON(
|
273 |
+
value=vehicle.model_dump_json(), label="Vehicle status"
|
274 |
+
)
|
275 |
+
with gr.Column():
|
276 |
+
output_audio = gr.Audio(label="output audio", autoplay=True)
|
277 |
+
output_text = gr.TextArea(value="", label="Output text", interactive=False)
|
278 |
+
# iface = gr.Interface(
|
279 |
+
# fn=transcript,
|
280 |
+
# inputs=[
|
281 |
+
# gr.Textbox(value=initial_context, visible=False),
|
282 |
+
# gr.Audio(type="filepath", label="input audio", elem_id="recorder"),
|
283 |
+
# voice_character,
|
284 |
+
# emotion,
|
285 |
+
# place,
|
286 |
+
# time_picker,
|
287 |
+
# history,
|
288 |
+
# gr.State(), # This will keep track of the context state across interactions.
|
289 |
+
# ],
|
290 |
+
# outputs=[gr.Audio(label="output audio"), gr.Textbox(visible=False), gr.State()],
|
291 |
+
# head=shortcut_js,
|
292 |
+
# )
|
293 |
+
|
294 |
+
# Update plot based on the origin and destination
|
295 |
+
# Sets the current location and destination
|
296 |
+
origin.submit(
|
297 |
+
fn=calculate_route_gradio,
|
298 |
+
inputs=[origin, destination],
|
299 |
+
outputs=[map_plot, vehicle_status],
|
300 |
+
)
|
301 |
+
destination.submit(
|
302 |
+
fn=calculate_route_gradio,
|
303 |
+
inputs=[origin, destination],
|
304 |
+
outputs=[map_plot, vehicle_status],
|
305 |
+
)
|
306 |
+
|
307 |
+
# Update time based on the time picker
|
308 |
+
time_picker.select(fn=set_time, inputs=[time_picker], outputs=[vehicle_status])
|
309 |
+
|
310 |
+
# Run the model if the input text is changed
|
311 |
+
input_text.submit(fn=run_model, inputs=[input_text, voice_character], outputs=[output_text, output_audio])
|
312 |
+
|
313 |
+
# Set the vehicle status based on the trip progress
|
314 |
+
trip_progress.release(
|
315 |
+
fn=update_vehicle_status, inputs=[trip_progress], outputs=[vehicle_status]
|
316 |
+
)
|
317 |
+
|
318 |
+
# Save and transcribe the audio
|
319 |
+
input_audio.stop_recording(
|
320 |
+
fn=save_and_transcribe_audio, inputs=[input_audio], outputs=[input_text]
|
321 |
+
)
|
322 |
+
|
323 |
+
# close all interfaces open to make the port available
|
324 |
+
gr.close_all()
|
325 |
+
# Launch the interface.
|
326 |
+
|
327 |
+
if __name__ == "__main__":
|
328 |
+
# demo.launch(debug=True, server_name="0.0.0.0", server_port=7860, ssl_verify=False)
|
329 |
+
demo.launch(debug=True, server_name="0.0.0.0", server_port=7860, ssl_verify=True, share=True)
|
330 |
+
|
331 |
+
# iface.launch(debug=True, share=False, server_name="0.0.0.0", server_port=7860, ssl_verify=False)
|
requirements.txt
CHANGED
@@ -1,11 +1,12 @@
|
|
1 |
torch
|
2 |
torchvision
|
|
|
3 |
matplotlib
|
4 |
wurlitzer
|
5 |
accelerate
|
6 |
bitsandbytes
|
7 |
optimum
|
8 |
-
auto-gptq
|
9 |
gradio
|
10 |
TTS
|
11 |
transformers==4.36
|
@@ -14,4 +15,8 @@ openai-whisper
|
|
14 |
geopy
|
15 |
langchain
|
16 |
text_generation
|
17 |
-
python-dotenv
|
|
|
|
|
|
|
|
|
|
1 |
torch
|
2 |
torchvision
|
3 |
+
torchaudio
|
4 |
matplotlib
|
5 |
wurlitzer
|
6 |
accelerate
|
7 |
bitsandbytes
|
8 |
optimum
|
9 |
+
# auto-gptq
|
10 |
gradio
|
11 |
TTS
|
12 |
transformers==4.36
|
|
|
15 |
geopy
|
16 |
langchain
|
17 |
text_generation
|
18 |
+
python-dotenv
|
19 |
+
pydantic-settings
|
20 |
+
ollama
|
21 |
+
langchain
|
22 |
+
plotly-express
|
skills/__init__.py
ADDED
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from datetime import datetime
|
2 |
+
import inspect
|
3 |
+
|
4 |
+
from .common import execute_function_call, extract_func_args, vehicle as vehicle_obj
|
5 |
+
from .weather import get_weather, get_forecast
|
6 |
+
from .routing import find_route
|
7 |
+
from .poi import search_points_of_interests, search_along_route_w_coordinates
|
8 |
+
from .vehicle import vehicle_status
|
9 |
+
|
10 |
+
|
11 |
+
|
12 |
+
def date_time_info():
|
13 |
+
"""Get the current date and time."""
|
14 |
+
time = getattr(vehicle_obj, "time")
|
15 |
+
date = getattr(vehicle_obj, "date")
|
16 |
+
datetime_obj = datetime.fromisoformat(f"{date}T{time}")
|
17 |
+
human_readable_datetime = datetime_obj.strftime("%I:%M %p %A, %B %d, %Y")
|
18 |
+
return f"It is {human_readable_datetime}."
|
19 |
+
|
20 |
+
|
21 |
+
def do_anything_else():
|
22 |
+
"""If the user wants to do anything else call this function. If the question doesn't match any of the functions use this one."""
|
23 |
+
return True
|
24 |
+
|
25 |
+
|
26 |
+
|
27 |
+
def format_functions_for_prompt_raven(*functions):
|
28 |
+
"""Format functions for use in Prompt Raven.
|
29 |
+
|
30 |
+
Args:
|
31 |
+
*functions (function): One or more functions to format.
|
32 |
+
"""
|
33 |
+
formatted_functions = []
|
34 |
+
for func in functions:
|
35 |
+
signature = f"{func.__name__}{inspect.signature(func)}"
|
36 |
+
docstring = inspect.getdoc(func)
|
37 |
+
formatted_functions.append(
|
38 |
+
f"Function:\n<func_start>{signature}<func_end>\n<docstring_start>\n{docstring}\n<docstring_end>"
|
39 |
+
)
|
40 |
+
return "\n".join(formatted_functions)
|
41 |
+
|
42 |
+
|
43 |
+
SKILLS_PROMPT = format_functions_for_prompt_raven(get_weather, get_forecast, find_route, search_points_of_interests)
|
skills/common.py
ADDED
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import re
|
2 |
+
from typing import Union
|
3 |
+
|
4 |
+
|
5 |
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
6 |
+
from pydantic import BaseModel
|
7 |
+
|
8 |
+
import skills
|
9 |
+
|
10 |
+
class Settings(BaseSettings):
|
11 |
+
WEATHER_API_KEY: str
|
12 |
+
TOMTOM_API_KEY: str
|
13 |
+
|
14 |
+
model_config = SettingsConfigDict(env_file=".env")
|
15 |
+
|
16 |
+
|
17 |
+
class VehicleStatus(BaseModel):
|
18 |
+
location: str
|
19 |
+
location_coordinates: tuple[float, float] # (latitude, longitude)
|
20 |
+
date: str
|
21 |
+
time: str
|
22 |
+
destination: str
|
23 |
+
|
24 |
+
|
25 |
+
def execute_function_call(text: str, dry_run=False) -> str:
|
26 |
+
function_name_match = re.search(r"Call: (\w+)", text)
|
27 |
+
function_name = function_name_match.group(1) if function_name_match else None
|
28 |
+
arguments = eval(f"dict{text.split(function_name)[1].strip()}")
|
29 |
+
function = getattr(skills, function_name) if function_name else None
|
30 |
+
|
31 |
+
if dry_run:
|
32 |
+
print(f"{function_name}(**{arguments})")
|
33 |
+
return "Dry run successful"
|
34 |
+
|
35 |
+
if function:
|
36 |
+
out = function(**arguments)
|
37 |
+
try:
|
38 |
+
if function:
|
39 |
+
out = function(**arguments)
|
40 |
+
except Exception as e:
|
41 |
+
out = str(e)
|
42 |
+
return out
|
43 |
+
|
44 |
+
|
45 |
+
def extract_func_args(text: str) -> tuple[str, dict]:
|
46 |
+
function_name_match = re.search(r"Call: (\w+)", text)
|
47 |
+
function_name = function_name_match.group(1) if function_name_match else None
|
48 |
+
if not function_name:
|
49 |
+
raise ValueError("No function name found in text")
|
50 |
+
arguments = eval(f"dict{text.split(function_name)[1].strip()}")
|
51 |
+
return function_name, arguments
|
52 |
+
|
53 |
+
|
54 |
+
config = Settings() # type: ignore
|
55 |
+
|
56 |
+
vehicle = VehicleStatus(
|
57 |
+
location="Rue Alphonse Weicker, Luxembourg",
|
58 |
+
location_coordinates=(49.505, 6.28111),
|
59 |
+
date="2025-05-06",
|
60 |
+
time="08:00:00",
|
61 |
+
destination="Rue Alphonse Weicker, Luxembourg"
|
62 |
+
)
|
skills/poi.py
ADDED
@@ -0,0 +1,143 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import json
|
2 |
+
import requests
|
3 |
+
from .common import config, vehicle
|
4 |
+
|
5 |
+
|
6 |
+
# Select coordinates at equal distance, including the last one
|
7 |
+
def select_equally_spaced_coordinates(coords, number_of_points=10):
|
8 |
+
n = len(coords)
|
9 |
+
selected_coords = []
|
10 |
+
interval = max((n - 1) / (number_of_points - 1), 1)
|
11 |
+
for i in range(number_of_points):
|
12 |
+
# Calculate the index, ensuring it doesn't exceed the bounds of the list
|
13 |
+
index = int(round(i * interval))
|
14 |
+
if index < n:
|
15 |
+
selected_coords.append(coords[index])
|
16 |
+
return selected_coords
|
17 |
+
|
18 |
+
|
19 |
+
def search_points_of_interests(search_query="french restaurant"):
|
20 |
+
"""
|
21 |
+
Return some of the closest points of interest matching the query.
|
22 |
+
:param search_query (string): Required. Describing the type of point of interest depending on what the user wants to do. Make sure to include the type of POI you are looking for. For example italian restaurant, grocery shop, etc.
|
23 |
+
"""
|
24 |
+
|
25 |
+
# Extract the latitude and longitude of the vehicle
|
26 |
+
vehicle_coordinates = getattr(vehicle, "location_coordinates")
|
27 |
+
lat, lon = vehicle_coordinates
|
28 |
+
print(f"POI search vehicle's lat: {lat}, lon: {lon}")
|
29 |
+
|
30 |
+
# https://developer.tomtom.com/search-api/documentation/search-service/search
|
31 |
+
r = requests.get(
|
32 |
+
f"https://api.tomtom.com/search/2/search/{search_query}.json?key={config.TOMTOM_API_KEY}&lat={lat}&lon={lon}&category&radius=1000&limit=100",
|
33 |
+
timeout=5,
|
34 |
+
)
|
35 |
+
|
36 |
+
# Parse JSON from the response
|
37 |
+
data = r.json()
|
38 |
+
# Extract results
|
39 |
+
results = data["results"]
|
40 |
+
|
41 |
+
# TODO: Handle the no results case.
|
42 |
+
if not results:
|
43 |
+
return "No results found in the vicinity."
|
44 |
+
|
45 |
+
# Sort the results based on distance
|
46 |
+
results = sorted(results, key=lambda x: x["dist"])
|
47 |
+
# print(sorted_results)
|
48 |
+
|
49 |
+
# Format and limit to top 5 results
|
50 |
+
formatted_results = [
|
51 |
+
f"{result['poi']['name']}, {int(result['dist'])} meters away"
|
52 |
+
for result in results[:3]
|
53 |
+
]
|
54 |
+
|
55 |
+
output = (
|
56 |
+
f"There are {len(results)} options in the vicinity. The most relevant are: "
|
57 |
+
)
|
58 |
+
return output + ".\n ".join(formatted_results)
|
59 |
+
|
60 |
+
|
61 |
+
def find_points_of_interest(lat="0", lon="0", type_of_poi="restaurant"):
|
62 |
+
"""
|
63 |
+
Return some of the closest points of interest for a specific location and type of point of interest. The more parameters there are, the more precise.
|
64 |
+
:param lat (string): latitude
|
65 |
+
:param lon (string): longitude
|
66 |
+
:param city (string): Required. city
|
67 |
+
:param type_of_poi (string): Required. type of point of interest depending on what the user wants to do.
|
68 |
+
"""
|
69 |
+
# https://developer.tomtom.com/search-api/documentation/search-service/points-of-interest-search
|
70 |
+
r = requests.get(
|
71 |
+
f"https://api.tomtom.com/search/2/search/{type_of_poi}"
|
72 |
+
".json?key={0}&lat={1}&lon={2}&radius=10000&vehicleTypeSet=Car&idxSet=POI&limit=100".format(
|
73 |
+
config.TOMTOM_API_KEY, lat, lon
|
74 |
+
)
|
75 |
+
)
|
76 |
+
|
77 |
+
# Parse JSON from the response
|
78 |
+
data = r.json()
|
79 |
+
# print(data)
|
80 |
+
# Extract results
|
81 |
+
results = data["results"]
|
82 |
+
|
83 |
+
# Sort the results based on distance
|
84 |
+
sorted_results = sorted(results, key=lambda x: x["dist"])
|
85 |
+
# print(sorted_results)
|
86 |
+
|
87 |
+
# Format and limit to top 5 results
|
88 |
+
formatted_results = [
|
89 |
+
f"The {type_of_poi} {result['poi']['name']}, {int(result['dist'])} meters away"
|
90 |
+
for result in sorted_results[:5]
|
91 |
+
]
|
92 |
+
|
93 |
+
return ". ".join(formatted_results)
|
94 |
+
|
95 |
+
|
96 |
+
def search_along_route_w_coordinates(points: list[tuple[float, float]], query: str):
|
97 |
+
"""
|
98 |
+
Return some of the closest points of interest along the route/way from the depart point, specified by its coordinates.
|
99 |
+
:param points (list[tuple(float, float)]): Required. List of tuples of latitude and longitude of the points along the route.
|
100 |
+
:param query (string): Required. type of point of interest depending on what the user wants to do.
|
101 |
+
"""
|
102 |
+
|
103 |
+
# The API endpoint for searching along a route
|
104 |
+
url = f"https://api.tomtom.com/search/2/searchAlongRoute/{query}.json?key={config.TOMTOM_API_KEY}&maxDetourTime=360&limit=20&sortBy=detourTime"
|
105 |
+
|
106 |
+
points = select_equally_spaced_coordinates(points, number_of_points=20)
|
107 |
+
|
108 |
+
# The data payload
|
109 |
+
payload = {
|
110 |
+
"route": {
|
111 |
+
"points": [{"lat": pt["latitude"], "lon": pt["longitude"]} for pt in points]
|
112 |
+
}
|
113 |
+
}
|
114 |
+
|
115 |
+
# Make the POST request
|
116 |
+
response = requests.post(url, json=payload, timeout=5)
|
117 |
+
|
118 |
+
# Check if the request was successful
|
119 |
+
if response.status_code == 200:
|
120 |
+
# Parse the JSON response
|
121 |
+
data = response.json()
|
122 |
+
# print(json.dumps(data, indent=4))
|
123 |
+
else:
|
124 |
+
print("Failed to retrieve data:", response.status_code)
|
125 |
+
return "Failed to retrieve data. Please try again."
|
126 |
+
answer = ""
|
127 |
+
if not data["results"]:
|
128 |
+
return "No results found along the way."
|
129 |
+
|
130 |
+
if len(data["results"]) == 20:
|
131 |
+
answer = "There more than 20 results along the way. Here are the top 3 results:"
|
132 |
+
elif len(data["results"]) > 3:
|
133 |
+
answer = f"There are {len(data['results'])} results along the way. Here are the top 3 results:"
|
134 |
+
for result in data["results"][:3]:
|
135 |
+
name = result["poi"]["name"]
|
136 |
+
address = result["address"]["freeformAddress"]
|
137 |
+
detour_time = result["detourTime"]
|
138 |
+
answer = (
|
139 |
+
answer
|
140 |
+
+ f" \n{name} at {address} would require a detour of {int(detour_time/60)} minutes."
|
141 |
+
)
|
142 |
+
|
143 |
+
return answer
|
skills/routing.py
ADDED
@@ -0,0 +1,167 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from datetime import datetime
|
2 |
+
import requests
|
3 |
+
from .common import config, vehicle
|
4 |
+
|
5 |
+
|
6 |
+
def find_coordinates(address):
|
7 |
+
"""
|
8 |
+
Find the coordinates of a specific address.
|
9 |
+
:param address (string): Required. The address
|
10 |
+
"""
|
11 |
+
# https://developer.tomtom.com/geocoding-api/documentation/geocode
|
12 |
+
url = f"https://api.tomtom.com/search/2/geocode/{address}.json?key={config.TOMTOM_API_KEY}"
|
13 |
+
response = requests.get(url)
|
14 |
+
data = response.json()
|
15 |
+
lat = data["results"][0]["position"]["lat"]
|
16 |
+
lon = data["results"][0]["position"]["lon"]
|
17 |
+
return lat, lon
|
18 |
+
|
19 |
+
|
20 |
+
def plot_route(points):
|
21 |
+
import plotly.express as px
|
22 |
+
|
23 |
+
lats = []
|
24 |
+
lons = []
|
25 |
+
|
26 |
+
for point in points:
|
27 |
+
lats.append(point["latitude"])
|
28 |
+
lons.append(point["longitude"])
|
29 |
+
# fig = px.line_geo(lat=lats, lon=lons)
|
30 |
+
# fig.update_geos(fitbounds="locations")
|
31 |
+
|
32 |
+
fig = px.line_mapbox(
|
33 |
+
lat=lats, lon=lons, zoom=12, height=600, color_discrete_sequence=["red"]
|
34 |
+
)
|
35 |
+
|
36 |
+
fig.update_layout(
|
37 |
+
mapbox_style="open-street-map",
|
38 |
+
# mapbox_zoom=12,
|
39 |
+
)
|
40 |
+
fig.update_geos(fitbounds="locations")
|
41 |
+
fig.update_layout(margin={"r": 20, "t": 20, "l": 20, "b": 20})
|
42 |
+
return fig
|
43 |
+
|
44 |
+
|
45 |
+
def calculate_route(origin, destination):
|
46 |
+
"""This function is called when the origin or destination is updated in the GUI. It calculates the route between the origin and destination."""
|
47 |
+
print(f"calculate_route(origin: {origin}, destination: {destination})")
|
48 |
+
origin_coords = find_coordinates(origin)
|
49 |
+
destination_coords = find_coordinates(destination)
|
50 |
+
|
51 |
+
orig_coords_str = ",".join(map(str, origin_coords))
|
52 |
+
dest_coords_str = ",".join(map(str, destination_coords))
|
53 |
+
print(f"origin_coords: {origin_coords}, destination_coords: {destination_coords}")
|
54 |
+
|
55 |
+
vehicle.destination = destination
|
56 |
+
vehicle.location_coordinates = origin_coords
|
57 |
+
vehicle.location = origin
|
58 |
+
|
59 |
+
# origin = "49.631997,6.171029"
|
60 |
+
# destination = "49.586745,6.140002"
|
61 |
+
|
62 |
+
url = f"https://api.tomtom.com/routing/1/calculateRoute/{orig_coords_str}:{dest_coords_str}/json?key={config.TOMTOM_API_KEY}"
|
63 |
+
response = requests.get(url)
|
64 |
+
data = response.json()
|
65 |
+
points = data["routes"][0]["legs"][0]["points"]
|
66 |
+
|
67 |
+
return plot_route(points), vehicle.model_dump_json(), points
|
68 |
+
|
69 |
+
|
70 |
+
def find_route_tomtom(
|
71 |
+
lat_depart="0",
|
72 |
+
lon_depart="0",
|
73 |
+
lat_dest="0",
|
74 |
+
lon_dest="0",
|
75 |
+
depart_datetime="",
|
76 |
+
**kwargs,
|
77 |
+
):
|
78 |
+
"""
|
79 |
+
Return the distance and the estimated time to go to a specific destination from the current place, at a specified depart time.
|
80 |
+
:param lat_depart (string): latitude of depart
|
81 |
+
:param lon_depart (string): longitude of depart
|
82 |
+
:param lat_dest (string): latitude of destination
|
83 |
+
:param lon_dest (string): longitude of destination
|
84 |
+
:param depart_time (string): departure hour, in the format '08:00:20'.
|
85 |
+
"""
|
86 |
+
# https://developer.tomtom.com/routing-api/documentation/routing/calculate-route
|
87 |
+
# https://developer.tomtom.com/routing-api/documentation/routing/guidance-instructions
|
88 |
+
url = f"https://api.tomtom.com/routing/1/calculateRoute/{lat_depart},{lon_depart}:{lat_dest},{lon_dest}/json?key={config.TOMTOM_API_KEY}&departAt={depart_datetime}"
|
89 |
+
|
90 |
+
print(f"Calling TomTom API: {url}")
|
91 |
+
r = requests.get(
|
92 |
+
url,
|
93 |
+
timeout=5,
|
94 |
+
)
|
95 |
+
|
96 |
+
# Parse JSON from the response
|
97 |
+
response = r.json()
|
98 |
+
|
99 |
+
try:
|
100 |
+
result = response["routes"][0]["summary"]
|
101 |
+
except KeyError:
|
102 |
+
print(f"Failed to find a route: {response}")
|
103 |
+
return "Failed to find a route", response
|
104 |
+
|
105 |
+
distance_m = result["lengthInMeters"]
|
106 |
+
duration_s = result["travelTimeInSeconds"]
|
107 |
+
arrival_time = result["arrivalTime"]
|
108 |
+
# Convert string to datetime object
|
109 |
+
arrival_time = datetime.fromisoformat(arrival_time)
|
110 |
+
|
111 |
+
return {
|
112 |
+
"distance_m": distance_m,
|
113 |
+
"duration_s": duration_s,
|
114 |
+
"arrival_time": arrival_time,
|
115 |
+
}, response
|
116 |
+
|
117 |
+
|
118 |
+
def find_route(destination=""):
|
119 |
+
"""This function finds a route to a destination and returns the distance and the estimated time to go to a specific destination\
|
120 |
+
from the current location.
|
121 |
+
:param destination (string): Required. The destination
|
122 |
+
"""
|
123 |
+
if not destination:
|
124 |
+
destination = vehicle.destination
|
125 |
+
|
126 |
+
# lat, lon, city = check_city_coordinates(lat_depart,lon_depart,city_depart)
|
127 |
+
lat_dest, lon_dest = find_coordinates(destination)
|
128 |
+
print(f"lat_dest: {lat_dest}, lon_dest: {lon_dest}")
|
129 |
+
|
130 |
+
# Extract the latitude and longitude of the vehicle
|
131 |
+
vehicle_coordinates = getattr(vehicle, "location_coordinates")
|
132 |
+
lat_depart, lon_depart = vehicle_coordinates
|
133 |
+
print(f"lat_depart: {lat_depart}, lon_depart: {lon_depart}")
|
134 |
+
|
135 |
+
date = getattr(vehicle, "date")
|
136 |
+
time = getattr(vehicle, "time")
|
137 |
+
departure_time = f"{date}T{time}"
|
138 |
+
|
139 |
+
trip_info, raw_response = find_route_tomtom(
|
140 |
+
lat_depart, lon_depart, lat_dest, lon_dest, departure_time
|
141 |
+
)
|
142 |
+
|
143 |
+
distance, duration, arrival_time = (
|
144 |
+
trip_info["distance_m"],
|
145 |
+
trip_info["duration_s"],
|
146 |
+
trip_info["arrival_time"],
|
147 |
+
)
|
148 |
+
|
149 |
+
# Calculate distance in kilometers (1 meter = 0.001 kilometers)
|
150 |
+
distance_km = distance * 0.001
|
151 |
+
# Calculate travel time in minutes (1 second = 1/60 minutes)
|
152 |
+
time_minutes = duration / 60
|
153 |
+
if time_minutes < 60:
|
154 |
+
time_display = f"{time_minutes:.0f} minutes"
|
155 |
+
else:
|
156 |
+
hours = int(time_minutes / 60)
|
157 |
+
minutes = int(time_minutes % 60)
|
158 |
+
time_display = f"{hours} hours" + (
|
159 |
+
f" and {minutes} minutes" if minutes > 0 else ""
|
160 |
+
)
|
161 |
+
|
162 |
+
# Extract and display the arrival hour in HH:MM format
|
163 |
+
arrival_hour_display = arrival_time.strftime("%H:%M")
|
164 |
+
|
165 |
+
# return the distance and time
|
166 |
+
return f"The route to {destination} is {distance_km:.2f} km which takes {time_display}. Leaving now, the arrival time is estimated at {arrival_hour_display}."
|
167 |
+
# raw_response["routes"][0]["legs"][0]["points"]
|
skills/vehicle.py
ADDED
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from .common import vehicle
|
2 |
+
|
3 |
+
|
4 |
+
STATUS_TEMPLATE = """
|
5 |
+
We are at {location}, coordinates: {lat}, {lon},
|
6 |
+
current time: {time}, current date: {date} and our destination is: {destination}.
|
7 |
+
"""
|
8 |
+
|
9 |
+
|
10 |
+
def vehicle_status() -> tuple[str, dict[str, str]]:
|
11 |
+
"""Get current vehicle status, which includes, location, date, time, destination.
|
12 |
+
Call this to get the current destination or location of the car/vehicle.
|
13 |
+
Returns:
|
14 |
+
dict[str, str]: The vehicle status. For example:
|
15 |
+
{
|
16 |
+
"location": "Luxembourg Gare, Luxembourg",
|
17 |
+
"lat": 49.6000,
|
18 |
+
"lon": 6.1333,
|
19 |
+
"date": "2025-03-29",
|
20 |
+
"time": "08:00:20",
|
21 |
+
"destination": "Kirchberg Campus, Kirchberg"
|
22 |
+
}
|
23 |
+
"""
|
24 |
+
# vs = {
|
25 |
+
# "location": "Luxembourg Gare, Luxembourg",
|
26 |
+
# "lat": 49.6000,
|
27 |
+
# "lon": 6.1333,
|
28 |
+
# "date": "2025-03-29",
|
29 |
+
# "time": "08:00:20",
|
30 |
+
# "destination": "Kirchberg Campus, Luxembourg"
|
31 |
+
# }
|
32 |
+
vs = vehicle.dict()
|
33 |
+
vs["lat"] = vs["location_coordinates"][0]
|
34 |
+
vs["lon"] = vs["location_coordinates"][1]
|
35 |
+
return STATUS_TEMPLATE.format(**vs), vs
|
skills/weather.py
ADDED
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import requests
|
2 |
+
|
3 |
+
from .common import config, vehicle
|
4 |
+
|
5 |
+
#current weather API
|
6 |
+
def get_weather(location:str= ""):
|
7 |
+
"""
|
8 |
+
Returns the CURRENT weather in a specified location.
|
9 |
+
Args:
|
10 |
+
location (string) : Required. The name of the location, could be a city or lat/longitude in the following format latitude,longitude (example: 37.7749,-122.4194). If the location is not specified, the function will return the weather in the current location.
|
11 |
+
"""
|
12 |
+
|
13 |
+
if location == "":
|
14 |
+
print(f"get_weather: location is empty, using the vehicle location. ({vehicle.location})")
|
15 |
+
location = vehicle.location
|
16 |
+
|
17 |
+
# The endpoint URL provided by WeatherAPI
|
18 |
+
url = f"http://api.weatherapi.com/v1/current.json?key={config.WEATHER_API_KEY}&q={location}&aqi=no"
|
19 |
+
print(url)
|
20 |
+
|
21 |
+
# Make the API request
|
22 |
+
response = requests.get(url)
|
23 |
+
|
24 |
+
if response.status_code != 200:
|
25 |
+
print(f"Failed to get weather data: {response.status_code}, {response.text}")
|
26 |
+
return f"Failed to get weather data, try again", response
|
27 |
+
|
28 |
+
# Parse the JSON response
|
29 |
+
weather_data = response.json()
|
30 |
+
|
31 |
+
# Extracting the necessary pieces of data
|
32 |
+
location = weather_data['location']['name']
|
33 |
+
region = weather_data['location']['region']
|
34 |
+
country = weather_data['location']['country']
|
35 |
+
time = weather_data['location']['localtime']
|
36 |
+
temperature_c = weather_data['current']['temp_c']
|
37 |
+
condition_text = weather_data['current']['condition']['text']
|
38 |
+
if 'wind_kph' in weather_data['current']:
|
39 |
+
wind_kph = weather_data['current']['wind_kph']
|
40 |
+
humidity = weather_data['current']['humidity']
|
41 |
+
feelslike_c = weather_data['current']['feelslike_c']
|
42 |
+
|
43 |
+
# Formulate the sentences - {region}, {country}
|
44 |
+
weather_sentences = (
|
45 |
+
f"The current weather in {location} is {condition_text} "
|
46 |
+
f"with a temperature of {temperature_c}°C that feels like {feelslike_c}°C. "
|
47 |
+
# f"Humidity is at {humidity}%. "
|
48 |
+
# f"Wind speed is {wind_kph} kph." if 'wind_kph' in weather_data['current'] else ""
|
49 |
+
)
|
50 |
+
return weather_sentences, weather_data
|
51 |
+
|
52 |
+
#weather forecast API
|
53 |
+
def get_forecast(city_name:str= "", when = 0, **kwargs):
|
54 |
+
"""
|
55 |
+
Returns the weather forecast in a specified number of days for a specified city .
|
56 |
+
Args:
|
57 |
+
city_name (string) : Required. The name of the city.
|
58 |
+
when (int) : Required. in number of days (until the day for which we want to know the forecast) (example: tomorrow is 1, in two days is 2, etc.)
|
59 |
+
"""
|
60 |
+
#print(when)
|
61 |
+
when +=1
|
62 |
+
# The endpoint URL provided by WeatherAPI
|
63 |
+
url = f"http://api.weatherapi.com/v1/forecast.json?key={WEATHER_API_KEY}&q={city_name}&days={str(when)}&aqi=no"
|
64 |
+
|
65 |
+
|
66 |
+
# Make the API request
|
67 |
+
response = requests.get(url)
|
68 |
+
|
69 |
+
if response.status_code == 200:
|
70 |
+
# Parse the JSON response
|
71 |
+
data = response.json()
|
72 |
+
|
73 |
+
# Initialize an empty string to hold our result
|
74 |
+
forecast_sentences = ""
|
75 |
+
|
76 |
+
# Extract city information
|
77 |
+
location = data.get('location', {})
|
78 |
+
city_name = location.get('name', 'the specified location')
|
79 |
+
|
80 |
+
#print(data)
|
81 |
+
|
82 |
+
|
83 |
+
# Extract the forecast days
|
84 |
+
forecast_days = data.get('forecast', {}).get('forecastday', [])[when-1:]
|
85 |
+
#number = 0
|
86 |
+
|
87 |
+
#print (forecast_days)
|
88 |
+
|
89 |
+
for day in forecast_days:
|
90 |
+
date = day.get('date', 'a specific day')
|
91 |
+
conditions = day.get('day', {}).get('condition', {}).get('text', 'weather conditions')
|
92 |
+
max_temp_c = day.get('day', {}).get('maxtemp_c', 'N/A')
|
93 |
+
min_temp_c = day.get('day', {}).get('mintemp_c', 'N/A')
|
94 |
+
chance_of_rain = day.get('day', {}).get('daily_chance_of_rain', 'N/A')
|
95 |
+
|
96 |
+
if when == 1:
|
97 |
+
number_str = 'today'
|
98 |
+
elif when == 2:
|
99 |
+
number_str = 'tomorrow'
|
100 |
+
else:
|
101 |
+
number_str = f'in {when-1} days'
|
102 |
+
|
103 |
+
# Generate a sentence for the day's forecast
|
104 |
+
forecast_sentence = f"On {date} ({number_str}) in {city_name}, the weather will be {conditions} with a high of {max_temp_c}°C and a low of {min_temp_c}°C. There's a {chance_of_rain}% chance of rain. "
|
105 |
+
|
106 |
+
#number = number + 1
|
107 |
+
# Add the sentence to the result
|
108 |
+
forecast_sentences += forecast_sentence
|
109 |
+
return forecast_sentences
|
110 |
+
else:
|
111 |
+
# Handle errors
|
112 |
+
print( f"Failed to get weather data: {response.status_code}, {response.text}")
|
113 |
+
return f'error {response.status_code}'
|
stt.ipynb
ADDED
@@ -0,0 +1,292 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"cells": [
|
3 |
+
{
|
4 |
+
"cell_type": "code",
|
5 |
+
"execution_count": 25,
|
6 |
+
"metadata": {},
|
7 |
+
"outputs": [
|
8 |
+
{
|
9 |
+
"name": "stderr",
|
10 |
+
"output_type": "stream",
|
11 |
+
"text": [
|
12 |
+
"/opt/homebrew/Caskroom/miniconda/base/envs/llm/lib/python3.11/site-packages/transformers/utils/generic.py:441: UserWarning: torch.utils._pytree._register_pytree_node is deprecated. Please use torch.utils._pytree.register_pytree_node instead.\n",
|
13 |
+
" _torch_pytree._register_pytree_node(\n",
|
14 |
+
"/opt/homebrew/Caskroom/miniconda/base/envs/llm/lib/python3.11/site-packages/transformers/utils/generic.py:309: UserWarning: torch.utils._pytree._register_pytree_node is deprecated. Please use torch.utils._pytree.register_pytree_node instead.\n",
|
15 |
+
" _torch_pytree._register_pytree_node(\n",
|
16 |
+
"/opt/homebrew/Caskroom/miniconda/base/envs/llm/lib/python3.11/site-packages/transformers/utils/generic.py:309: UserWarning: torch.utils._pytree._register_pytree_node is deprecated. Please use torch.utils._pytree.register_pytree_node instead.\n",
|
17 |
+
" _torch_pytree._register_pytree_node(\n"
|
18 |
+
]
|
19 |
+
}
|
20 |
+
],
|
21 |
+
"source": [
|
22 |
+
"#STT (speech to text)\n",
|
23 |
+
"from transformers import WhisperProcessor, WhisperForConditionalGeneration\n",
|
24 |
+
"from transformers import pipeline\n",
|
25 |
+
"import librosa"
|
26 |
+
]
|
27 |
+
},
|
28 |
+
{
|
29 |
+
"cell_type": "code",
|
30 |
+
"execution_count": 3,
|
31 |
+
"metadata": {},
|
32 |
+
"outputs": [
|
33 |
+
{
|
34 |
+
"name": "stderr",
|
35 |
+
"output_type": "stream",
|
36 |
+
"text": [
|
37 |
+
"Special tokens have been added in the vocabulary, make sure the associated word embeddings are fine-tuned or trained.\n"
|
38 |
+
]
|
39 |
+
}
|
40 |
+
],
|
41 |
+
"source": [
|
42 |
+
"# load model and processor for speech-to-text\n",
|
43 |
+
"processor = WhisperProcessor.from_pretrained(\"openai/whisper-small\")\n",
|
44 |
+
"modelw = WhisperForConditionalGeneration.from_pretrained(\"openai/whisper-small\")\n",
|
45 |
+
"# modelw.config.forced_decoder_ids = None"
|
46 |
+
]
|
47 |
+
},
|
48 |
+
{
|
49 |
+
"cell_type": "code",
|
50 |
+
"execution_count": 51,
|
51 |
+
"metadata": {},
|
52 |
+
"outputs": [],
|
53 |
+
"source": [
|
54 |
+
"device = \"cuda\" if torch.cuda.is_available() else \"cpu\""
|
55 |
+
]
|
56 |
+
},
|
57 |
+
{
|
58 |
+
"cell_type": "code",
|
59 |
+
"execution_count": 52,
|
60 |
+
"metadata": {},
|
61 |
+
"outputs": [
|
62 |
+
{
|
63 |
+
"name": "stderr",
|
64 |
+
"output_type": "stream",
|
65 |
+
"text": [
|
66 |
+
"Special tokens have been added in the vocabulary, make sure the associated word embeddings are fine-tuned or trained.\n"
|
67 |
+
]
|
68 |
+
}
|
69 |
+
],
|
70 |
+
"source": [
|
71 |
+
"transcriber = pipeline(\"automatic-speech-recognition\", model=\"openai/whisper-base.en\", device=device)"
|
72 |
+
]
|
73 |
+
},
|
74 |
+
{
|
75 |
+
"cell_type": "code",
|
76 |
+
"execution_count": 53,
|
77 |
+
"metadata": {},
|
78 |
+
"outputs": [
|
79 |
+
{
|
80 |
+
"data": {
|
81 |
+
"text/plain": [
|
82 |
+
"device(type='cpu')"
|
83 |
+
]
|
84 |
+
},
|
85 |
+
"execution_count": 53,
|
86 |
+
"metadata": {},
|
87 |
+
"output_type": "execute_result"
|
88 |
+
}
|
89 |
+
],
|
90 |
+
"source": [
|
91 |
+
"transcriber.device"
|
92 |
+
]
|
93 |
+
},
|
94 |
+
{
|
95 |
+
"cell_type": "code",
|
96 |
+
"execution_count": 58,
|
97 |
+
"metadata": {},
|
98 |
+
"outputs": [],
|
99 |
+
"source": [
|
100 |
+
"audio = \"/Users/sasan.jafarnejad/dev/uni/talking-car/audio1713724521.779008.wav\"\n",
|
101 |
+
"audio = \"/Users/sasan.jafarnejad/dev/uni/talking-car/audio/attenborough/neutral.wav\"\n",
|
102 |
+
"if type(audio) == str:\n",
|
103 |
+
" link_to_audio = audio\n",
|
104 |
+
" audio = torchaudio.load(link_to_audio)\n",
|
105 |
+
" audio = audio[0].squeeze().numpy(), sr\n",
|
106 |
+
" if len(audio[0].shape) == 2:\n",
|
107 |
+
" audio = audio[0].mean(axis=0), audio[1]"
|
108 |
+
]
|
109 |
+
},
|
110 |
+
{
|
111 |
+
"cell_type": "code",
|
112 |
+
"execution_count": 59,
|
113 |
+
"metadata": {},
|
114 |
+
"outputs": [],
|
115 |
+
"source": [
|
116 |
+
"y, sr = audio"
|
117 |
+
]
|
118 |
+
},
|
119 |
+
{
|
120 |
+
"cell_type": "code",
|
121 |
+
"execution_count": 60,
|
122 |
+
"metadata": {},
|
123 |
+
"outputs": [],
|
124 |
+
"source": [
|
125 |
+
"out = transcriber({\"sampling_rate\": sr, \"raw\":y})[\"text\"]"
|
126 |
+
]
|
127 |
+
},
|
128 |
+
{
|
129 |
+
"cell_type": "code",
|
130 |
+
"execution_count": 61,
|
131 |
+
"metadata": {},
|
132 |
+
"outputs": [
|
133 |
+
{
|
134 |
+
"data": {
|
135 |
+
"text/plain": [
|
136 |
+
"\" If you speed up time, plants begin to reveal their true nature. They're not passive organisms, as you might think, but competitive creatures every bit as aggressive as animals. They're locked in a desperate battle for light and space. They stretch and pulse as they strive to barge their way into pole position. Creepers and vines reach around for the branch or stem of another plant on which to hitch a ride. This is an assassin bug. To us, it's easy enough to spot because it moves. To its prey, that's irrelevant, because it smells like one of their number. The assassin sucks its victims dry and blues their empty husks onto its back. This one is already carrying at least 20 corpses. Its irregular shape makes it hard for other predators to spot it and makes it virtually invisible to its prey, ants. It enters this ant colony unchallenged. Its coat of ant corpses masks its own odour. To the ants, it smells like one of their own, and that's what matters. They'll even run straight over the top of it. The assassin simply takes an ant whenever it feels hungry, and the body of each victim then adds to its disguise. The giants here too. This is the Moala Moala, the sunfish. It's huge, three meters across, and addicted to lying on its side at the surface. It eats vast quantities of jellyfish. And there are not only fish from me in these waters, there are mammals. sea lions, whose ancestors originally came from the coasts of California. The Galapagos plankton is so abundant it attracts some of the biggest of all ocean mammals, humpback whales, and rivalling them in size the biggest of all fish. 20 ton whale shark. Few parts of the world's oceans can equally as Galapagos waters for sheer variety and abundance. That creature was a penguin. Penguins are ocean-goings for moose. But a few thousand years ago some of them got caught in the cold waters of the Humboldt current and were carried northwards up the coast of South America and out to the Galapagos. They could hardly have found anywhere more different from their polar home and in and the response they chain, the Emperor penguin that lives near the South Pole stands over a metre high, Galapagos penguin is now only half, and that helps a lot in the Galapagos. Small animals lose heat much faster than big ones, and the penguins have developed behavioral tricks as well. Bear feet are easily sunburnt, so they do their best to keep them covered, and some parts of the sea around the islands are quite cool. The humbalt current flowing up from the Antarctic and washing around the western parts of the archipelago is still quite chilly. So most of the penguins stay in the channel between the two westernmost island and when things get really hot, they can still cool off with the swim. They're quick to detect the slightest variation in temperature and move around to find places where an eddy might have brought a pleasing chill. The arrival of penguins must be the most unlikely event in the whole story of the colonization of the Galapagos. The existence of creatures like these so far from the nearest continent poses many questions. How, for example, did these enormous beasts get to the islands in the first place? But perhaps the most extraordinary thing about the Galapagos tortoises is that they're not all the same. islands have different kinds. In the heyday there were 15 species. They seem to have appeared in an evolutionary blink of the eye. But will soon become a leaf. It's no ordinary. It has a special altogether more sinister. This is Nepenthes, the pitcher plant. It grows in nutrient poor soils, So has to find nitrogen and minerals in another way. The leaf, just like a flower, attracts insects with a reward. The pitcher is coloured and scented to appeal to flies looking for a meal of rotting flesh. The visitors are rewarded with a greasy substance on the underside of the pitcher's lid. But the plant wants something in return, not pollen, but a meal. The lip of the pitcher is covered in tiny slippery ridges. Wax lubricates the surface further. It's extremely difficult to hold on, even for a fly. Once inside, there's no escape. The leaf holds a pool of digestive liquid. This contains microscopic elastic filaments, which give it the properties of quicksand. The more the insect struggles, the deeper it sinks. Enzymes begin to dissolve the victim's body while it's still alive.\""
|
137 |
+
]
|
138 |
+
},
|
139 |
+
"execution_count": 61,
|
140 |
+
"metadata": {},
|
141 |
+
"output_type": "execute_result"
|
142 |
+
}
|
143 |
+
],
|
144 |
+
"source": [
|
145 |
+
"out"
|
146 |
+
]
|
147 |
+
},
|
148 |
+
{
|
149 |
+
"cell_type": "code",
|
150 |
+
"execution_count": null,
|
151 |
+
"metadata": {},
|
152 |
+
"outputs": [],
|
153 |
+
"source": [
|
154 |
+
"def transcript(audio):\n",
|
155 |
+
" if type(audio) == str:\n",
|
156 |
+
" link_to_audio = audio\n",
|
157 |
+
" audio = torchaudio.load(link_to_audio)\n",
|
158 |
+
"\n",
|
159 |
+
" # We assume that the audio is the audio tensor\n",
|
160 |
+
"\n",
|
161 |
+
" # process the audio array\n",
|
162 |
+
" input_features = processor(audio_array, sampling_rate, return_tensors=\"pt\").input_features\n",
|
163 |
+
" predicted_ids = modelw.generate(input_features)\n",
|
164 |
+
"\n",
|
165 |
+
" transcription = processor.batch_decode(predicted_ids, skip_special_tokens=True)\n",
|
166 |
+
" \n",
|
167 |
+
" return audio_path, state['context'], state"
|
168 |
+
]
|
169 |
+
},
|
170 |
+
{
|
171 |
+
"cell_type": "code",
|
172 |
+
"execution_count": null,
|
173 |
+
"metadata": {},
|
174 |
+
"outputs": [],
|
175 |
+
"source": [
|
176 |
+
"def transcript(general_context, link_to_audio, voice, place, time, delete_history, state):\n",
|
177 |
+
" \"\"\"this function manages speech-to-text to input Fnanswer function and text-to-speech with the Fnanswer output\"\"\"\n",
|
178 |
+
" # load audio from a specific path\n",
|
179 |
+
" audio_path = link_to_audio\n",
|
180 |
+
" audio_array, sampling_rate = librosa.load(link_to_audio, sr=16000) # \"sr=16000\" ensures that the sampling rate is as required\n",
|
181 |
+
"\n",
|
182 |
+
" # process the audio array\n",
|
183 |
+
" input_features = processor(audio_array, sampling_rate, return_tensors=\"pt\").input_features\n",
|
184 |
+
" predicted_ids = modelw.generate(input_features)\n",
|
185 |
+
"\n",
|
186 |
+
" transcription = processor.batch_decode(predicted_ids, skip_special_tokens=True)\n",
|
187 |
+
" \n",
|
188 |
+
"\n",
|
189 |
+
"\n",
|
190 |
+
" return audio_path, state['context'], state"
|
191 |
+
]
|
192 |
+
},
|
193 |
+
{
|
194 |
+
"cell_type": "code",
|
195 |
+
"execution_count": 64,
|
196 |
+
"metadata": {},
|
197 |
+
"outputs": [
|
198 |
+
{
|
199 |
+
"name": "stdout",
|
200 |
+
"output_type": "stream",
|
201 |
+
"text": [
|
202 |
+
"Running on local URL: http://127.0.0.1:7875\n",
|
203 |
+
"\n",
|
204 |
+
"To create a public link, set `share=True` in `launch()`.\n"
|
205 |
+
]
|
206 |
+
},
|
207 |
+
{
|
208 |
+
"data": {
|
209 |
+
"text/html": [
|
210 |
+
"<div><iframe src=\"http://127.0.0.1:7875/\" width=\"100%\" height=\"500\" allow=\"autoplay; camera; microphone; clipboard-read; clipboard-write;\" frameborder=\"0\" allowfullscreen></iframe></div>"
|
211 |
+
],
|
212 |
+
"text/plain": [
|
213 |
+
"<IPython.core.display.HTML object>"
|
214 |
+
]
|
215 |
+
},
|
216 |
+
"metadata": {},
|
217 |
+
"output_type": "display_data"
|
218 |
+
},
|
219 |
+
{
|
220 |
+
"data": {
|
221 |
+
"text/plain": []
|
222 |
+
},
|
223 |
+
"execution_count": 64,
|
224 |
+
"metadata": {},
|
225 |
+
"output_type": "execute_result"
|
226 |
+
}
|
227 |
+
],
|
228 |
+
"source": [
|
229 |
+
"import numpy as np\n",
|
230 |
+
"import gradio as gr\n",
|
231 |
+
"import torchaudio\n",
|
232 |
+
"import time\n",
|
233 |
+
"import torch\n",
|
234 |
+
"\n",
|
235 |
+
"def save_audio_as_wav(data, sample_rate, file_path):\n",
|
236 |
+
" # make a tensor from the numpy array\n",
|
237 |
+
" data = torch.tensor(data).reshape(1, -1)\n",
|
238 |
+
" torchaudio.save(file_path, data, sample_rate=sample_rate, bits_per_sample=16, encoding=\"PCM_S\")\n",
|
239 |
+
"\n",
|
240 |
+
"def save_and_transcribe_audio(audio):\n",
|
241 |
+
" # capture the audio and save it to a file as wav or mp3\n",
|
242 |
+
" # file_name = save(\"audioinput.wav\")\n",
|
243 |
+
" sr, y = audio\n",
|
244 |
+
" # y = y.astype(np.float32)\n",
|
245 |
+
" # y /= np.max(np.abs(y))\n",
|
246 |
+
"\n",
|
247 |
+
" # add timestamp to file name\n",
|
248 |
+
" filename = f\"audio{time.time()}.wav\"\n",
|
249 |
+
" save_audio_as_wav(y, sr, filename)\n",
|
250 |
+
" \n",
|
251 |
+
" sr, y = audio\n",
|
252 |
+
" y = y.astype(np.float32)\n",
|
253 |
+
" y /= np.max(np.abs(y))\n",
|
254 |
+
" text = transcriber({\"sampling_rate\": sr, \"raw\":y})[\"text\"]\n",
|
255 |
+
" return text\n",
|
256 |
+
"\n",
|
257 |
+
"gr.Interface(\n",
|
258 |
+
" fn=save_and_transcribe_audio, \n",
|
259 |
+
" inputs=gr.Audio(sources=\"microphone\", type=\"numpy\", label=\"Record Audio\"), \n",
|
260 |
+
" outputs=\"text\").launch()"
|
261 |
+
]
|
262 |
+
},
|
263 |
+
{
|
264 |
+
"cell_type": "code",
|
265 |
+
"execution_count": null,
|
266 |
+
"metadata": {},
|
267 |
+
"outputs": [],
|
268 |
+
"source": []
|
269 |
+
}
|
270 |
+
],
|
271 |
+
"metadata": {
|
272 |
+
"kernelspec": {
|
273 |
+
"display_name": "llm",
|
274 |
+
"language": "python",
|
275 |
+
"name": "python3"
|
276 |
+
},
|
277 |
+
"language_info": {
|
278 |
+
"codemirror_mode": {
|
279 |
+
"name": "ipython",
|
280 |
+
"version": 3
|
281 |
+
},
|
282 |
+
"file_extension": ".py",
|
283 |
+
"mimetype": "text/x-python",
|
284 |
+
"name": "python",
|
285 |
+
"nbconvert_exporter": "python",
|
286 |
+
"pygments_lexer": "ipython3",
|
287 |
+
"version": "3.11.8"
|
288 |
+
}
|
289 |
+
},
|
290 |
+
"nbformat": 4,
|
291 |
+
"nbformat_minor": 2
|
292 |
+
}
|
tts.ipynb
ADDED
The diff for this file is too large to render.
See raw diff
|
|