1
1
mirror of https://github.com/leon-ai/leon.git synced 2024-12-25 17:54:43 +03:00

feat: new onFetch skill API and more

This commit is contained in:
louistiti 2024-09-01 23:16:14 +08:00
parent 9aa9e0ba49
commit 8bca0595b4
No known key found for this signature in database
GPG Key ID: 92CD6A2E497E1669
16 changed files with 160 additions and 144 deletions

View File

@ -128,7 +128,7 @@ export default class Chatbot {
} }
const data = await axios.get( const data = await axios.get(
`${this.serverURL}/api/v1/fetch-widget?skill_action=${widgetContainer.onFetchAction}&widget_id=${widgetContainer.widgetId}` `${this.serverURL}/api/v1/fetch-widget?skill_action=${widgetContainer.onFetch.actionName}&widget_id=${widgetContainer.widgetId}`
) )
const fetchedWidget = data.data.widget const fetchedWidget = data.data.widget
const reactNode = fetchedWidget const reactNode = fetchedWidget
@ -201,7 +201,7 @@ export default class Chatbot {
/** /**
* On widget fetching, render the loader * On widget fetching, render the loader
*/ */
if (isCreatingFromLoadingFeed && parsedWidget.onFetchAction) { if (isCreatingFromLoadingFeed && parsedWidget.onFetch) {
const root = createRoot(container) const root = createRoot(container)
root.render( root.render(
@ -217,7 +217,7 @@ export default class Chatbot {
WIDGETS_TO_FETCH.push({ WIDGETS_TO_FETCH.push({
reactRootNode: root, reactRootNode: root,
widgetId: parsedWidget.id, widgetId: parsedWidget.id,
onFetchAction: parsedWidget.onFetchAction onFetch: parsedWidget.onFetch
}) })
return return

View File

@ -133,7 +133,7 @@ class Leon {
actionName: `${INTENT_OBJECT.domain}:${INTENT_OBJECT.skill}:${INTENT_OBJECT.action}`, actionName: `${INTENT_OBJECT.domain}:${INTENT_OBJECT.skill}:${INTENT_OBJECT.action}`,
widget: answerInput.widget.widget, widget: answerInput.widget.widget,
id: answerInput.widget.id, id: answerInput.widget.id,
onFetchAction: answerInput.widget.onFetchAction, onFetch: answerInput.widget.onFetch ?? null,
componentTree: new WidgetWrapper({ componentTree: new WidgetWrapper({
...answerInput.widget.wrapperProps, ...answerInput.widget.wrapperProps,
children: [answerInput.widget.render()] children: [answerInput.widget.render()]

View File

@ -1,7 +1,7 @@
import { INTENT_OBJECT } from '@bridge/constants' import { INTENT_OBJECT } from '@bridge/constants'
/** /**
* Get the widget ID if any * Get the widget id if any
* @example getWidgetId() // 'timerwidget-5q1xlzeh * @example getWidgetId() // 'timerwidget-5q1xlzeh
*/ */
export function getWidgetId(): string | null { export function getWidgetId(): string | null {

View File

@ -5,11 +5,6 @@ import { WidgetComponent } from '@sdk/widget-component'
type UtteranceSender = 'leon' | 'owner' type UtteranceSender = 'leon' | 'owner'
interface FetchWidgetDataWidgetEventMethodParams {
actionName: string
widgetId: string
dataToSet: string[]
}
interface SendUtteranceWidgetEventMethodParams { interface SendUtteranceWidgetEventMethodParams {
from: UtteranceSender from: UtteranceSender
utterance: string utterance: string
@ -28,11 +23,13 @@ export interface WidgetEventMethod {
methodParams: methodParams:
| SendUtteranceWidgetEventMethodParams | SendUtteranceWidgetEventMethodParams
| RunSkillActionWidgetEventMethodParams | RunSkillActionWidgetEventMethodParams
| FetchWidgetDataWidgetEventMethodParams
} }
export interface WidgetOptions<T = unknown> { export interface WidgetOptions<T = unknown> {
wrapperProps?: Omit<WidgetWrapperProps, 'children'> wrapperProps?: Omit<WidgetWrapperProps, 'children'>
onFetchAction?: string onFetch?: {
widgetId?: string | undefined
actionName: string
}
params: T params: T
} }
@ -40,7 +37,7 @@ export abstract class Widget<T = unknown> {
public actionName: string public actionName: string
public id: string public id: string
public widget: string public widget: string
public onFetchAction: string | null = null public onFetch: WidgetOptions<T>['onFetch'] | null = null
public wrapperProps: WidgetOptions<T>['wrapperProps'] public wrapperProps: WidgetOptions<T>['wrapperProps']
public params: WidgetOptions<T>['params'] public params: WidgetOptions<T>['params']
@ -48,15 +45,20 @@ export abstract class Widget<T = unknown> {
if (options?.wrapperProps) { if (options?.wrapperProps) {
this.wrapperProps = options.wrapperProps this.wrapperProps = options.wrapperProps
} }
if (options?.onFetchAction) {
this.onFetchAction = `${INTENT_OBJECT.domain}:${INTENT_OBJECT.skill}:${options.onFetchAction}`
}
this.actionName = `${INTENT_OBJECT.domain}:${INTENT_OBJECT.skill}:${INTENT_OBJECT.action}` this.actionName = `${INTENT_OBJECT.domain}:${INTENT_OBJECT.skill}:${INTENT_OBJECT.action}`
this.params = options.params
this.widget = this.constructor.name this.widget = this.constructor.name
this.id = `${this.widget.toLowerCase()}-${Math.random() if (options?.onFetch) {
this.onFetch = {
widgetId: options.onFetch.widgetId,
actionName: `${INTENT_OBJECT.domain}:${INTENT_OBJECT.skill}:${options.onFetch.actionName}`
}
}
this.id =
options.onFetch?.widgetId ||
`${this.widget.toLowerCase()}-${Math.random()
.toString(36) .toString(36)
.substring(2, 10)}` .substring(2, 10)}`
this.params = options.params
} }
/** /**

View File

@ -84,12 +84,12 @@ class Leon:
'actionName': f"{INTENT_OBJECT['domain']}:{INTENT_OBJECT['skill']}:{INTENT_OBJECT['action']}", 'actionName': f"{INTENT_OBJECT['domain']}:{INTENT_OBJECT['skill']}:{INTENT_OBJECT['action']}",
'widget': widget.widget, 'widget': widget.widget,
'id': widget.id, 'id': widget.id,
'onFetchAction': widget.on_fetch_action, 'onFetch': widget.on_fetch if hasattr(widget, 'on_fetch') else None,
'componentTree': WidgetWrapper({ 'componentTree': WidgetWrapper({
**wrapper_props, **wrapper_props,
'children': [widget.render()] 'children': [widget.render()]
}).__dict__(), }).__dict__(),
'supportedEvents': SUPPORTED_WIDGET_EVENTS # You might need to define this constant 'supportedEvents': SUPPORTED_WIDGET_EVENTS
} }
answer_object = { answer_object = {

View File

@ -2,6 +2,7 @@ from typing import Any, Optional, Generic, TypeVar, Literal, TypedDict, Union, D
from dataclasses import dataclass from dataclasses import dataclass
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
import random import random
import string
from .widget_component import WidgetComponent from .widget_component import WidgetComponent
from ..constants import SKILL_CONFIG, INTENT_OBJECT from ..constants import SKILL_CONFIG, INTENT_OBJECT
@ -11,12 +12,6 @@ T = TypeVar('T')
UtteranceSender = Literal['leon', 'owner'] UtteranceSender = Literal['leon', 'owner']
class FetchWidgetDataWidgetEventMethodParams(TypedDict):
action_name: str
widget_id: str
data_to_set: list[str]
class SendUtteranceWidgetEventMethodParams(TypedDict): class SendUtteranceWidgetEventMethodParams(TypedDict):
from_: UtteranceSender from_: UtteranceSender
utterance: str utterance: str
@ -36,8 +31,7 @@ class WidgetEventMethod(TypedDict):
methodName: Literal['send_utterance', 'run_skill_action'] methodName: Literal['send_utterance', 'run_skill_action']
methodParams: Union[ methodParams: Union[
SendUtteranceWidgetEventMethodParams, SendUtteranceWidgetEventMethodParams,
RunSkillActionWidgetEventMethodParams, RunSkillActionWidgetEventMethodParams
FetchWidgetDataWidgetEventMethodParams
] ]
@ -45,18 +39,23 @@ class WidgetEventMethod(TypedDict):
class WidgetOptions(Generic[T]): class WidgetOptions(Generic[T]):
wrapper_props: dict[str, Any] = None wrapper_props: dict[str, Any] = None
params: T = None params: T = None
on_fetch_action: Optional[str] = None on_fetch: Optional[dict[str, Any]] = None
class Widget(ABC, Generic[T]): class Widget(ABC, Generic[T]):
def __init__(self, options: WidgetOptions[T]): def __init__(self, options: WidgetOptions[T]):
self.action_name = f"{INTENT_OBJECT['domain']}:{INTENT_OBJECT['skill']}:{INTENT_OBJECT['action']}" if options.wrapper_props:
self.widget = self.__class__.__name__
self.id = f"{self.widget.lower()}-{random.randint(100000, 999999)}"
self.wrapper_props = options.wrapper_props self.wrapper_props = options.wrapper_props
self.action_name = f"{INTENT_OBJECT['domain']}:{INTENT_OBJECT['skill']}:{INTENT_OBJECT['action']}"
self.params = options.params self.params = options.params
self.on_fetch_action = f"{INTENT_OBJECT['domain']}:{INTENT_OBJECT['skill']}:{options.on_fetch_action}" \ self.widget = self.__class__.__name__
if options.on_fetch_action else None if options.on_fetch:
self.on_fetch = {
'widgetId': options.on_fetch.get('widget_id'),
'actionName': f"{INTENT_OBJECT['domain']}:{INTENT_OBJECT['skill']}:{options.on_fetch.get('action_name')}"
}
self.id = options.on_fetch.get('widget_id') if options.on_fetch \
else f"{self.widget.lower()}-{''.join(random.choices(string.ascii_lowercase + string.digits, k=8))}"
@abstractmethod @abstractmethod
def render(self) -> WidgetComponent: def render(self) -> WidgetComponent:

View File

@ -1,4 +1,6 @@
from typing import TypeVar, Generic, TypedDict, List, Any from typing import TypeVar, Generic, TypedDict, List, Any
import random
import string
T = TypeVar('T') T = TypeVar('T')
@ -12,8 +14,6 @@ SUPPORTED_WIDGET_EVENTS = [
def generate_id() -> str: def generate_id() -> str:
import random
import string
return ''.join(random.choices(string.ascii_lowercase + string.digits, k=5)) return ''.join(random.choices(string.ascii_lowercase + string.digits, k=5))

View File

@ -52,64 +52,6 @@
"route": "/api/action/games/rochambeau/rematch", "route": "/api/action/games/rochambeau/rematch",
"params": [] "params": []
}, },
{
"method": "POST",
"route": "/api/action/news/github_trends/run",
"params": ["number", "daterange"],
"entitiesType": "builtIn"
},
{
"method": "GET",
"route": "/api/action/news/product_hunt_trends/run",
"params": []
},
{
"method": "POST",
"route": "/api/action/productivity/todo_list/create_list",
"params": ["list"],
"entitiesType": "trim"
},
{
"method": "GET",
"route": "/api/action/productivity/todo_list/view_lists",
"params": []
},
{
"method": "POST",
"route": "/api/action/productivity/todo_list/view_list",
"params": ["list"],
"entitiesType": "trim"
},
{
"method": "POST",
"route": "/api/action/productivity/todo_list/rename_list",
"params": ["old_list", "new_list"],
"entitiesType": "trim"
},
{
"method": "POST",
"route": "/api/action/productivity/todo_list/delete_list",
"params": ["list"],
"entitiesType": "trim"
},
{
"method": "POST",
"route": "/api/action/productivity/todo_list/add_todos",
"params": ["todos", "list"],
"entitiesType": "trim"
},
{
"method": "POST",
"route": "/api/action/productivity/todo_list/complete_todos",
"params": ["todos", "list"],
"entitiesType": "trim"
},
{
"method": "POST",
"route": "/api/action/productivity/todo_list/uncheck_todos",
"params": ["todos", "list"],
"entitiesType": "trim"
},
{ {
"method": "GET", "method": "GET",
"route": "/api/action/leon/age/run", "route": "/api/action/leon/age/run",
@ -185,11 +127,64 @@
"route": "/api/action/leon/thanks/run", "route": "/api/action/leon/thanks/run",
"params": [] "params": []
}, },
{
"method": "POST",
"route": "/api/action/news/github_trends/run",
"params": ["number", "daterange"],
"entitiesType": "builtIn"
},
{ {
"method": "GET", "method": "GET",
"route": "/api/action/unknown/widget-playground/run", "route": "/api/action/news/product_hunt_trends/run",
"params": [] "params": []
}, },
{
"method": "POST",
"route": "/api/action/productivity/todo_list/create_list",
"params": ["list"],
"entitiesType": "trim"
},
{
"method": "GET",
"route": "/api/action/productivity/todo_list/view_lists",
"params": []
},
{
"method": "POST",
"route": "/api/action/productivity/todo_list/view_list",
"params": ["list"],
"entitiesType": "trim"
},
{
"method": "POST",
"route": "/api/action/productivity/todo_list/rename_list",
"params": ["old_list", "new_list"],
"entitiesType": "trim"
},
{
"method": "POST",
"route": "/api/action/productivity/todo_list/delete_list",
"params": ["list"],
"entitiesType": "trim"
},
{
"method": "POST",
"route": "/api/action/productivity/todo_list/add_todos",
"params": ["todos", "list"],
"entitiesType": "trim"
},
{
"method": "POST",
"route": "/api/action/productivity/todo_list/complete_todos",
"params": ["todos", "list"],
"entitiesType": "trim"
},
{
"method": "POST",
"route": "/api/action/productivity/todo_list/uncheck_todos",
"params": ["todos", "list"],
"entitiesType": "trim"
},
{ {
"method": "GET", "method": "GET",
"route": "/api/action/social_communication/conversation/setup", "route": "/api/action/social_communication/conversation/setup",
@ -215,6 +210,11 @@
"route": "/api/action/social_communication/mbti/quiz", "route": "/api/action/social_communication/mbti/quiz",
"params": [] "params": []
}, },
{
"method": "GET",
"route": "/api/action/unknown/widget-playground/run",
"params": []
},
{ {
"method": "GET", "method": "GET",
"route": "/api/action/utilities/date_time/current_date_time", "route": "/api/action/utilities/date_time/current_date_time",

View File

@ -98,7 +98,10 @@ export interface SkillAnswerOutput extends IntentObject {
id: string id: string
componentTree: WidgetWrapper componentTree: WidgetWrapper
supportedEvents: typeof SUPPORTED_WIDGET_EVENTS supportedEvents: typeof SUPPORTED_WIDGET_EVENTS
onFetchAction: string | null onFetch: {
widgetId?: string
actionName: string
} | null
} }
} }
} }

View File

@ -1,5 +1,7 @@
from bridges.python.src.sdk.leon import leon from bridges.python.src.sdk.leon import leon
from bridges.python.src.sdk.types import ActionParams from bridges.python.src.sdk.types import ActionParams
from bridges.python.src.sdk.widget import WidgetOptions
from ..widgets.todos_list_widget import TodosListWidget
from ..lib import memory from ..lib import memory
from typing import Union from typing import Union
@ -25,7 +27,11 @@ def run(params: ActionParams) -> None:
} }
}) })
memory.create_todo_list(list_name) todos_list_widget = TodosListWidget(WidgetOptions())
memory.create_todo_list(
todos_list_widget.id,
list_name
)
leon.answer({ leon.answer({
'key': 'list_created', 'key': 'list_created',

View File

@ -1,7 +1,8 @@
from bridges.python.src.sdk.leon import leon from bridges.python.src.sdk.leon import leon
from bridges.python.src.sdk.toolbox import get_widget_id
from bridges.python.src.sdk.types import ActionParams from bridges.python.src.sdk.types import ActionParams
from bridges.python.src.sdk.widget import WidgetOptions from bridges.python.src.sdk.widget import WidgetOptions
from ..widgets.todos_list_widget import TodosListWidget, TodosListWidgetParams from ..widgets.todos_list_widget import TodosListWidget
from ..lib import memory from ..lib import memory
from typing import Union from typing import Union
@ -10,6 +11,7 @@ from typing import Union
def run(params: ActionParams) -> None: def run(params: ActionParams) -> None:
"""View a to-do list""" """View a to-do list"""
widget_id = get_widget_id()
list_name: Union[str, None] = None list_name: Union[str, None] = None
for item in params['entities']: for item in params['entities']:
@ -27,6 +29,7 @@ def run(params: ActionParams) -> None:
} }
}) })
# TODO: if widget_id, get list by widget_id, otherwise get by list_name
todos = memory.get_todo_items(list_name) todos = memory.get_todo_items(list_name)
if len(todos) == 0: if len(todos) == 0:
@ -37,10 +40,15 @@ def run(params: ActionParams) -> None:
} }
}) })
todos_list_options: WidgetOptions[TodosListWidgetParams] = WidgetOptions( todos_list_widget = TodosListWidget(
WidgetOptions(
wrapper_props={'noPadding': True}, wrapper_props={'noPadding': True},
params={'list_name': list_name, 'todos': todos} params={'list_name': list_name, 'todos': todos},
on_fetch={
'widget_id': widget_id,
'action_name': 'view_list'
}
)
) )
todos_list_widget = TodosListWidget(todos_list_options)
leon.answer({'widget': todos_list_widget}) leon.answer({'widget': todos_list_widget})

View File

@ -15,13 +15,14 @@ todo_items_memory = Memory({
}) })
class TodoList(TypedDict): class TodoListMemory(TypedDict):
widget_id: str
name: str name: str
created_at: str created_at: str
updated_at: str updated_at: str
class TodoItem(TypedDict): class TodoItemMemory(TypedDict):
todo_list_name: str todo_list_name: str
name: str name: str
is_completed: bool is_completed: bool
@ -29,21 +30,22 @@ class TodoItem(TypedDict):
updated_at: str updated_at: str
def create_todo_list(name: str) -> None: def create_todo_list(widget_id: str, name: str) -> None:
"""Create a new todo list""" """Create a new todo list"""
datetime_now = datetime.now().isoformat() datetime_now = datetime.now().isoformat()
todo_list = TodoList( todo_list = TodoListMemory(
widget_id=widget_id,
name=name, name=name,
created_at=datetime_now, created_at=datetime_now,
updated_at=datetime_now updated_at=datetime_now
) )
todo_lists: list[TodoList] = todo_lists_memory.read() todo_lists: list[TodoListMemory] = todo_lists_memory.read()
todo_lists.append(todo_list) todo_lists.append(todo_list)
todo_lists_memory.write(todo_lists) todo_lists_memory.write(todo_lists)
def get_todo_lists() -> list[TodoList]: def get_todo_lists() -> list[TodoListMemory]:
"""Get all todo lists""" """Get all todo lists"""
return todo_lists_memory.read() return todo_lists_memory.read()
@ -52,7 +54,7 @@ def get_todo_lists() -> list[TodoList]:
def update_todo_list(old_name: str, new_name: str) -> None: def update_todo_list(old_name: str, new_name: str) -> None:
"""Update a todo list name""" """Update a todo list name"""
todo_lists: list[TodoList] = todo_lists_memory.read() todo_lists: list[TodoListMemory] = todo_lists_memory.read()
for todo_list in todo_lists: for todo_list in todo_lists:
if todo_list['name'] == old_name: if todo_list['name'] == old_name:
todo_list['name'] = new_name todo_list['name'] = new_name
@ -60,7 +62,7 @@ def update_todo_list(old_name: str, new_name: str) -> None:
break break
todo_lists_memory.write(todo_lists) todo_lists_memory.write(todo_lists)
todo_items: list[TodoItem] = todo_items_memory.read() todo_items: list[TodoItemMemory] = todo_items_memory.read()
for todo_item in todo_items: for todo_item in todo_items:
if todo_item['todo_list_name'] == old_name: if todo_item['todo_list_name'] == old_name:
todo_item['todo_list_name'] = new_name todo_item['todo_list_name'] = new_name
@ -71,14 +73,14 @@ def update_todo_list(old_name: str, new_name: str) -> None:
def delete_todo_list(name: str) -> None: def delete_todo_list(name: str) -> None:
"""Delete a todo list and its todos""" """Delete a todo list and its todos"""
todo_lists: list[TodoList] = todo_lists_memory.read() todo_lists: list[TodoListMemory] = todo_lists_memory.read()
for todo_list in todo_lists: for todo_list in todo_lists:
if todo_list['name'] == name: if todo_list['name'] == name:
todo_lists.remove(todo_list) todo_lists.remove(todo_list)
break break
todo_lists_memory.write(todo_lists) todo_lists_memory.write(todo_lists)
todo_items: list[TodoItem] = todo_items_memory.read() todo_items: list[TodoItemMemory] = todo_items_memory.read()
for todo_item in todo_items: for todo_item in todo_items:
if todo_item['todo_list_name'] == name: if todo_item['todo_list_name'] == name:
todo_items.remove(todo_item) todo_items.remove(todo_item)
@ -94,7 +96,7 @@ def count_todo_lists() -> int:
def has_todo_list(name: str) -> bool: def has_todo_list(name: str) -> bool:
"""Check if a todo list already exist""" """Check if a todo list already exist"""
todo_lists: list[TodoList] = todo_lists_memory.read() todo_lists: list[TodoListMemory] = todo_lists_memory.read()
for todo_list in todo_lists: for todo_list in todo_lists:
if todo_list['name'] == name: if todo_list['name'] == name:
return True return True
@ -107,22 +109,22 @@ def create_todo_item(todo_list_name: str, name: str) -> None:
if not has_todo_list(todo_list_name): if not has_todo_list(todo_list_name):
create_todo_list(todo_list_name) create_todo_list(todo_list_name)
datetime_now = datetime.now().isoformat() datetime_now = datetime.now().isoformat()
todo_item = TodoItem( todo_item = TodoItemMemory(
todo_list_name=todo_list_name, todo_list_name=todo_list_name,
name=name, name=name,
is_completed=False, is_completed=False,
created_at=datetime_now, created_at=datetime_now,
updated_at=datetime_now updated_at=datetime_now
) )
todo_items: list[TodoItem] = todo_items_memory.read() todo_items: list[TodoItemMemory] = todo_items_memory.read()
todo_items.append(todo_item) todo_items.append(todo_item)
todo_items_memory.write(todo_items) todo_items_memory.write(todo_items)
def get_todo_items(todo_list_name: str) -> list[TodoItem]: def get_todo_items(todo_list_name: str) -> list[TodoItemMemory]:
"""Get all todo items of a todo list""" """Get all todo items of a todo list"""
todo_items: list[TodoItem] = todo_items_memory.read() todo_items: list[TodoItemMemory] = todo_items_memory.read()
return [todo_item for todo_item in todo_items if todo_item['todo_list_name'] == todo_list_name] return [todo_item for todo_item in todo_items if todo_item['todo_list_name'] == todo_list_name]
@ -132,24 +134,24 @@ def count_todo_items(todo_list_name: str) -> int:
return len(get_todo_items(todo_list_name)) return len(get_todo_items(todo_list_name))
def get_completed_todo_items(todo_list_name: str) -> list[TodoItem]: def get_completed_todo_items(todo_list_name: str) -> list[TodoItemMemory]:
"""Get all completed todo items of a todo list""" """Get all completed todo items of a todo list"""
todo_items: list[TodoItem] = todo_items_memory.read() todo_items: list[TodoItemMemory] = todo_items_memory.read()
return [todo_item for todo_item in todo_items if todo_item['todo_list_name'] == todo_list_name and todo_item['is_completed']] return [todo_item for todo_item in todo_items if todo_item['todo_list_name'] == todo_list_name and todo_item['is_completed']]
def get_uncompleted_todo_items(todo_list_name: str) -> list[TodoItem]: def get_uncompleted_todo_items(todo_list_name: str) -> list[TodoItemMemory]:
"""Get all uncompleted todo items of a todo list""" """Get all uncompleted todo items of a todo list"""
todo_items: list[TodoItem] = todo_items_memory.read() todo_items: list[TodoItemMemory] = todo_items_memory.read()
return [todo_item for todo_item in todo_items if todo_item['todo_list_name'] == todo_list_name and not todo_item['is_completed']] return [todo_item for todo_item in todo_items if todo_item['todo_list_name'] == todo_list_name and not todo_item['is_completed']]
def complete_todo_item(todo_list_name: str, name: str) -> None: def complete_todo_item(todo_list_name: str, name: str) -> None:
"""Complete a todo item""" """Complete a todo item"""
todo_items: list[TodoItem] = todo_items_memory.read() todo_items: list[TodoItemMemory] = todo_items_memory.read()
for todo_item in todo_items: for todo_item in todo_items:
if todo_item['todo_list_name'] == todo_list_name and todo_item['name'] == name: if todo_item['todo_list_name'] == todo_list_name and todo_item['name'] == name:
todo_item['is_completed'] = True todo_item['is_completed'] = True
@ -161,7 +163,7 @@ def complete_todo_item(todo_list_name: str, name: str) -> None:
def uncomplete_todo_item(todo_list_name: str, name: str) -> None: def uncomplete_todo_item(todo_list_name: str, name: str) -> None:
"""Uncomplete a todo item""" """Uncomplete a todo item"""
todo_items: list[TodoItem] = todo_items_memory.read() todo_items: list[TodoItemMemory] = todo_items_memory.read()
for todo_item in todo_items: for todo_item in todo_items:
if todo_item['todo_list_name'] == todo_list_name and todo_item['name'] == name: if todo_item['todo_list_name'] == todo_list_name and todo_item['name'] == name:
todo_item['is_completed'] = False todo_item['is_completed'] = False

View File

@ -14,6 +14,7 @@ class TodoType(TypedDict):
class TodosListWidgetParams(TypedDict): class TodosListWidgetParams(TypedDict):
id: str
list_name: str list_name: str
todos: list[TodoType] todos: list[TodoType]

View File

@ -24,13 +24,15 @@ export const run: ActionFunction = async function () {
const timerWidget = new TimerWidget({ const timerWidget = new TimerWidget({
params: { params: {
id: widgetId ?? timerMemory.widgetId,
seconds: remainingTime, seconds: remainingTime,
initialProgress, initialProgress,
initialDuration: duration, initialDuration: duration,
interval interval
}, },
onFetchAction: 'check_timer' onFetch: {
widgetId: widgetId ?? timerMemory.widgetId,
actionName: 'check_timer'
}
}) })
await leon.answer({ widget: timerWidget }) await leon.answer({ widget: timerWidget })

View File

@ -29,18 +29,16 @@ export const run: ActionFunction = async function (params) {
initialProgress: 0, initialProgress: 0,
interval interval
}, },
onFetchAction: 'check_timer' onFetch: {
actionName: 'check_timer'
}
}) })
await createTimerMemory(timerWidget.id, seconds, interval) await Promise.all([
createTimerMemory(timerWidget.id, seconds, interval),
// TODO: return a speech without new utterance leon.answer({
/*await leon.answer({
widget: timerWidget,
speech: 'I set a timer for ... ...'
})*/
await leon.answer({
widget: timerWidget, widget: timerWidget,
key: 'timer_set' key: 'timer_set'
}) })
])
} }

View File

@ -8,16 +8,11 @@ interface Params {
interval: number interval: number
initialProgress: number initialProgress: number
initialDuration?: number initialDuration?: number
id?: string
} }
export class TimerWidget extends Widget<Params> { export class TimerWidget extends Widget<Params> {
constructor(options: WidgetOptions<Params>) { constructor(options: WidgetOptions<Params>) {
super(options) super(options)
if (options.params.id) {
this.id = options.params.id
}
} }
public render(): WidgetComponent { public render(): WidgetComponent {