chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"$schema": "../../../schemas/skill-schemas/skill-locale-config.json",
|
||||
"variables": {
|
||||
"hours": "hours",
|
||||
"minutes": "minutes",
|
||||
"seconds": "seconds"
|
||||
},
|
||||
"actions": {
|
||||
"set_timer": {
|
||||
"answers": {
|
||||
"timer_set": [
|
||||
{
|
||||
"speech": "Done. I will let you know when time is up."
|
||||
}
|
||||
],
|
||||
"cannot_get_duration": [
|
||||
"You should provide a duration for the timer.",
|
||||
"You didn't provide a duration for the timer."
|
||||
],
|
||||
"unit_not_supported": [
|
||||
"Sorry, I can't set a timer for this unit. Use {{ hours }}, {{ minutes }} or {{ seconds }} instead.",
|
||||
"I can't set a timer for this duration. Use {{ hours }}, {{ minutes }} or {{ seconds }} instead."
|
||||
]
|
||||
}
|
||||
},
|
||||
"check_timer": {
|
||||
"answers": {
|
||||
"no_timer_set": ["No timer is set.", "There is no timer set."],
|
||||
"timer_status": [
|
||||
"The timer has {{ remaining_time }} seconds remaining.",
|
||||
"Timer in progress. {{ remaining_time }} seconds left."
|
||||
]
|
||||
}
|
||||
},
|
||||
"cancel_timer": {
|
||||
"answers": {
|
||||
"timer_canceled": ["The timer is canceled.", "Timer is stopped."]
|
||||
}
|
||||
}
|
||||
},
|
||||
"widget_contents": {
|
||||
"second_unit": "second",
|
||||
"seconds_unit": "seconds",
|
||||
"minutes_unit": "minutes",
|
||||
"minute_unit": "minute",
|
||||
"total_time": "Total {{ value }} {{ unit }}",
|
||||
"times_up": ["Time's up!", "The timer is up!", "The timer has ended!"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"$schema": "../../../schemas/skill-schemas/skill.json",
|
||||
"name": "Timer",
|
||||
"icon_name": "timer-line",
|
||||
"bridge": "nodejs",
|
||||
"version": "1.0.0",
|
||||
"description": "Set timers to remind you of things.",
|
||||
"author": {
|
||||
"name": "Louis Grenard",
|
||||
"email": "louis@getleon.ai",
|
||||
"url": "https://github.com/louistiti"
|
||||
},
|
||||
"actions": {
|
||||
"set_timer": {
|
||||
"type": "logic",
|
||||
"description": "Set a timer for a specified duration.",
|
||||
"parameters": {
|
||||
"duration": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"value": {
|
||||
"type": "number",
|
||||
"description": "The numeric value of the duration."
|
||||
},
|
||||
"unit": {
|
||||
"type": "string",
|
||||
"enum": ["HOURS", "MINUTES", "SECONDS"],
|
||||
"description": "The unit of time for the duration."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"cancel_timer": {
|
||||
"type": "logic",
|
||||
"description": "Cancel an active timer."
|
||||
},
|
||||
"check_timer": {
|
||||
"type": "logic",
|
||||
"description": "Check the remaining time on the active timer."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import type { ActionFunction } from '@sdk/types'
|
||||
import { leon } from '@sdk/leon'
|
||||
|
||||
import { deleteAllTimersMemory } from '../lib/memory'
|
||||
|
||||
export const run: ActionFunction = async function () {
|
||||
await deleteAllTimersMemory()
|
||||
|
||||
await leon.answer({ key: 'timer_canceled' })
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import type { ActionFunction } from '@sdk/types'
|
||||
import { leon } from '@sdk/leon'
|
||||
|
||||
import { TimerWidget } from '../widgets/timer-widget'
|
||||
import { getTimerMemoryByWidgetId, getNewestTimerMemory } from '../lib/memory'
|
||||
|
||||
export const run: ActionFunction = async function (_params, paramsHelper) {
|
||||
const widgetId = paramsHelper.getWidgetId()
|
||||
const timerMemory = widgetId
|
||||
? await getTimerMemoryByWidgetId(widgetId)
|
||||
: await getNewestTimerMemory()
|
||||
|
||||
if (!timerMemory) {
|
||||
await leon.answer({ key: 'no_timer_set' })
|
||||
return
|
||||
}
|
||||
|
||||
const { interval, finishedAt, duration } = timerMemory
|
||||
let remainingTime = finishedAt - Math.floor(Date.now() / 1_000)
|
||||
if (remainingTime <= 0) {
|
||||
remainingTime = 0
|
||||
}
|
||||
const initialProgress = 100 - (remainingTime / duration) * 100
|
||||
|
||||
const timerWidget = new TimerWidget({
|
||||
params: {
|
||||
seconds: remainingTime,
|
||||
initialProgress,
|
||||
initialDuration: duration,
|
||||
interval
|
||||
},
|
||||
onFetch: {
|
||||
widgetId: widgetId ?? timerMemory.widgetId,
|
||||
actionName: 'check_timer'
|
||||
}
|
||||
})
|
||||
|
||||
await leon.answer({
|
||||
widget: timerWidget,
|
||||
key: 'timer_status',
|
||||
data: {
|
||||
remaining_time: remainingTime
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import type { ActionFunction } from '@sdk/types'
|
||||
import { leon } from '@sdk/leon'
|
||||
|
||||
import { TimerWidget } from '../widgets/timer-widget'
|
||||
import { createTimerMemory } from '../lib/memory'
|
||||
|
||||
interface TimerDuration {
|
||||
value?: number
|
||||
unit?: string
|
||||
}
|
||||
|
||||
export const run: ActionFunction = async function (params) {
|
||||
const supportedUnits = ['hours', 'minutes', 'seconds']
|
||||
const duration = (params.action_arguments['duration'] as TimerDuration) || null
|
||||
|
||||
if (
|
||||
!duration ||
|
||||
typeof duration.value !== 'number' ||
|
||||
typeof duration.unit !== 'string'
|
||||
) {
|
||||
await leon.answer({ key: 'cannot_get_duration' })
|
||||
return
|
||||
}
|
||||
|
||||
const normalizedUnit = duration.unit.toLowerCase()
|
||||
if (!supportedUnits.includes(normalizedUnit)) {
|
||||
await leon.answer({ key: 'unit_not_supported' })
|
||||
return
|
||||
}
|
||||
|
||||
const { value: durationValue } = duration
|
||||
const seconds =
|
||||
normalizedUnit === 'hours'
|
||||
? Number(durationValue) * 3_600
|
||||
: normalizedUnit === 'minutes'
|
||||
? Number(durationValue) * 60
|
||||
: Number(durationValue)
|
||||
const interval = 1_000
|
||||
const timerWidget = new TimerWidget({
|
||||
params: {
|
||||
seconds,
|
||||
initialProgress: 0,
|
||||
interval
|
||||
},
|
||||
onFetch: {
|
||||
actionName: 'check_timer'
|
||||
}
|
||||
})
|
||||
|
||||
await Promise.all([
|
||||
createTimerMemory(timerWidget.id, seconds, interval),
|
||||
leon.answer({
|
||||
widget: timerWidget,
|
||||
key: 'timer_set'
|
||||
})
|
||||
])
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import { Memory } from '@sdk/memory'
|
||||
|
||||
export interface TimerMemory {
|
||||
widgetId: string
|
||||
duration: number
|
||||
interval: number
|
||||
createdAt: number
|
||||
finishedAt: number
|
||||
}
|
||||
|
||||
const TIMERS_MEMORY = new Memory<TimerMemory[]>({
|
||||
name: 'timers',
|
||||
defaultMemory: []
|
||||
})
|
||||
|
||||
export async function createTimerMemory(
|
||||
widgetId: string,
|
||||
duration: number,
|
||||
interval: number
|
||||
): Promise<TimerMemory> {
|
||||
const createdAt = Math.floor(Date.now() / 1_000)
|
||||
const newTimerMemory: TimerMemory = {
|
||||
duration,
|
||||
widgetId,
|
||||
interval,
|
||||
createdAt,
|
||||
finishedAt: createdAt + duration
|
||||
}
|
||||
|
||||
const timersMemory = await TIMERS_MEMORY.read()
|
||||
await TIMERS_MEMORY.write([...timersMemory, newTimerMemory])
|
||||
|
||||
return newTimerMemory
|
||||
}
|
||||
|
||||
export async function getTimerMemoryByWidgetId(
|
||||
widgetId: string
|
||||
): Promise<TimerMemory | null> {
|
||||
const timersMemory = await TIMERS_MEMORY.read()
|
||||
|
||||
return (
|
||||
timersMemory.find((timerMemory) => timerMemory.widgetId === widgetId) ||
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
export async function getNewestTimerMemory(): Promise<TimerMemory | null> {
|
||||
const timersMemory = await TIMERS_MEMORY.read()
|
||||
|
||||
return timersMemory[timersMemory.length - 1] || null
|
||||
}
|
||||
|
||||
export function deleteAllTimersMemory(): Promise<TimerMemory[]> {
|
||||
return TIMERS_MEMORY.write([])
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -0,0 +1,16 @@
|
||||
import type { WidgetEventMethod } from '@sdk/widget'
|
||||
import { WidgetComponent } from '@sdk/widget-component'
|
||||
|
||||
interface TimerProps {
|
||||
initialTime: number
|
||||
initialProgress: number
|
||||
interval: number
|
||||
totalTimeContent: string
|
||||
onEnd?: () => WidgetEventMethod
|
||||
}
|
||||
|
||||
export class Timer extends WidgetComponent<TimerProps> {
|
||||
constructor(props: TimerProps) {
|
||||
super(props)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import type { WidgetComponent } from '@sdk/widget-component'
|
||||
import { Widget, type WidgetEventMethod, type WidgetOptions } from '@sdk/widget'
|
||||
|
||||
import { Timer } from './components/timer'
|
||||
|
||||
interface Params {
|
||||
seconds: number
|
||||
interval: number
|
||||
initialProgress: number
|
||||
initialDuration?: number
|
||||
}
|
||||
|
||||
export class TimerWidget extends Widget<Params> {
|
||||
constructor(options: WidgetOptions<Params>) {
|
||||
super(options)
|
||||
}
|
||||
|
||||
public render(): WidgetComponent {
|
||||
const { seconds, interval, initialDuration, initialProgress } = this.params
|
||||
const secondUnitContent = this.content('second_unit')
|
||||
const secondsUnitContent = this.content('seconds_unit')
|
||||
const minuteUnitContent = this.content('minute_unit')
|
||||
const minutesUnitContent = this.content('minutes_unit')
|
||||
const totalTime = initialDuration || seconds
|
||||
let totalTimeContent = ''
|
||||
|
||||
if (totalTime >= 60) {
|
||||
const minutes = totalTime / 60
|
||||
|
||||
totalTimeContent = this.content('total_time', {
|
||||
value: minutes % 1 === 0 ? minutes : minutes.toFixed(2),
|
||||
unit: minutes > 1 ? minutesUnitContent : minuteUnitContent
|
||||
})
|
||||
} else {
|
||||
totalTimeContent = this.content('total_time', {
|
||||
value: totalTime,
|
||||
unit: totalTime > 1 ? secondsUnitContent : secondUnitContent
|
||||
})
|
||||
}
|
||||
|
||||
return new Timer({
|
||||
initialTime: seconds,
|
||||
initialProgress,
|
||||
interval,
|
||||
totalTimeContent,
|
||||
onEnd: (): WidgetEventMethod => {
|
||||
return this.sendUtterance('times_up', {
|
||||
from: 'leon'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user