Giter VIP home page Giter VIP logo

yandexdialogs's Introduction

Яндекс.Диалоги для Home Assistant

Компонент позволяет управлять Home Assistant из Яндекс Алисы через собственный навык в Яндекс.Диалогах.

С помощью Яндекс.Диалогов вы можете настроить реакцию Алисы на абсолютно любые фразы. А не только те, что заложили разработчики. Яндекс Алиса работает на колонках, мобильных приложениях Яндекса и на компьютере в браузере Яндекса.

Особенностью управления через Диалоги является необходимость называть имя навыка:

  • Алиса, узнай у Умного дома температуру в спальне
  • Алиса, попроси Мой дом включить Ютуб на телевизоре в зале
  • Алиса, узнай у Домашнего ассистента когда было последнее движение у входной двери

Для начала диалога без использования имени навыка есть два метода:

  • Создать сценарий Яндекса, например: если я скажу "включи мультики на телевизоре", умная колонка выполнит команду "скажи навыку умный дом включи мультики на ТВ"
  • Начинать диалог из Home Assistant с помощью компонента Yandex.Station

Для работы компонента нужен рабочий внешний доступ к вашему Home Assistant по протоколу HTTPS. Его можно получить через другой мой компонент - Dataplicity.

Не стоит путать Яндекс.Диалоги с Умным домом Яндекса. Это разные технологии, не связанные между собой.


Установка

Способ 1. HACS > Интеграции > 3 точки (правый верхний угол) > Пользовательские репозитории > URL: AlexxIT/YandexDialogs, Категория: Интеграция > Добавить > подождать > YandexDialogs > Установить

Способ 2. Вручную скопируйте папку yandex_dialogs из latest release в директорию /config/custom_components.

Настройка

Способ 1. GUI

Настройки > Интеграции > Добавить интеграцию > Yandex Dialogs

Если интеграции нет в списке - очистите кэш браузера.

Способ 2. YAML

yandex_dialogs:

Использование

Если у вас уже работает компонент YandexStation и есть внешний доступ по HTTPS - этот компонент может автоматически создать и настроить навык в Яндекс.Диалогах.

Для этого ещё раз добавьте интеграцию:

Настройки > Интеграции > Добавить интеграцию > Yandex Dialogs

И укажите:

  • аккаунт Яндекса, от имени которого создавать Диалог
  • публичную HTTPS-ссылку на ваш сервер Home Assistant
  • имя навыка (Яндекс требует имя из двух слов)

Компонент автоматически создаст новый приватный диалог, опубликует его и сохранит идентификатор вашего пользователя в настройки интеграции. Навык публикуется в течение нескольких минут!

По умолчанию приватный навык доступен только вашему пользователю. Но для дополнительной безопасности можно ограничить доступ только списку пользователей. Идентификаторы пользователей уникальны для связки пользователь+навык и выглядят примерно так: ABCDEF01234567890ABCDEF01234567890ABCDEF01234567890ABCDEF0123456. Если список пользователей пуст - дополнительная проверка выключена.

При необходимости вы можете создать несколько диалогов с разными именами.

PS: При желании можете самостоятельно создать приватный навык с Webhook на ваш Home Assistant: https://myhome.duckdns.org/api/yandex_dialogs

Управление

Поддерживается обработка команд диалога разными способами:

Управление через автоматизации на событиях

Внимание: у вас несколько секунд, чтоб вызвать событие с текстовым результатом ответа.

Этот подход можно использовать в Node-RED.

При обращении к навыку создаётся событие yandex_intent с параметрами:

  • text - произнесённая фраза
  • command - фраза, почищенная от знаков препинания и числетельные преобразованы в числа
  • intent - интент это обозначение "типовой" фразы. Например, для ответов "да" или "хорошо" здесь будет значение YANDEX.CONFIRM. При желании вы можете настраивать свои интенты (описано ниже)
  • session, user, application - "хранилище" состояний диалога
  • ... - другие переменные, которые вы прописали в Интенте в Яндекс.Диалогах

Для ответа вы должны сами вызвать событие yandex_intent_response с параметрами:

  • text - опциональный, текст ответа
  • tts - опциональный, ответ в формате TTS
  • end_session, опциональный, по умолчанию True, "выйти" из диалога после ответа
  • session, user, application - опциональные, новое значение "хранилища" состояний. Состояния session и application не переносятся между разными шагами диалога автоматически!

Для ответа вы можете заполнить или text или tts в зависимости от того, нужно ли вам произнести ответ со спецэффектами TTS.

automation:
- trigger:
    platform: event
    event_type: yandex_intent  # это событие ОТ вашего навыка
    event_data:
      text: привет  # проверяем точное совпадение с фразой "привет"
  action:
    event: yandex_intent_response  # это наш ответ навыку, нужно уложиться в пару секунд
    event_data:
      text: "{{ ['слушаю', 'здесь я', 'на связи']|random }}"

- trigger:
    platform: event
    event_type: yandex_intent
    event_data:
      intent: calc  # проверяем на совпадение с Интентом калькулятора
  action:
  - service: persistent_notification.create
    data:
      title: Яндекс Калькулятор
      message: "{{ trigger.event.data.text }}"
  - event: yandex_intent_response
    event_data:  # есть все переменные, как и в примере выше
      text: >-
        {% if trigger.event.data.action == 'плюс' %}
          {{ trigger.event.data.x + trigger.event.data.y }}
        {% elif trigger.event.data.action == 'минус' %}
          {{ trigger.event.data.x - trigger.event.data.y }}
        {% elif trigger.event.data.action == 'умножить на' %}
          {{ trigger.event.data.x * trigger.event.data.y }}
        {% elif trigger.event.data.action == 'разделить на' %}
          {{ trigger.event.data.x / trigger.event.data.y }}
        {% endif %}

Управление продолжением диалога

Фраза "Алиса, включи навык Умный дома" включит навык и навык будет ждать вашей команды.

Фраза "Алиса, спроси у Умного дома сколько градусов в зале" - вызовет ваш навык, получит ответ и тут же выйдет из него назад к Алисе.

Чтоб изменить это поведение, используйте параметр end_session. С ним вы можете либо продолжить разговор при фразе "Алиса спроси у Умного дома...". Либо прервать диалог в любом месте.

Управление через Intent Script

Альтернативный способ управления диалогом. Более сложный и не рекомендуется к использованию.

Существует скрипт по умолчанию yandex_default. Он выполняется когда для фразы не совпал ни один Интент.

action опциональный. Он выполняется ДО генерации ответа и при желании может на него повлиять.

Внимание: у вашего скрипта пара секунд, чтоб вернуть ответ. Алиса дольше не ждёт. Если ваш скрипт выполняется дольше - запускайте его ассинхронно (читайте документацию).

Вам доступны переменные:

  • text - произнесённая фраза
  • command - фраза, почищенная от знаков препинания и числетельные преобразованы в числа
  • intent - если фраза совпала, тут будет ID Интента из Яндекс.Диалогов
  • ... - другие переменные, которые вы прописали в Интенте в Яндекс.Диалогах
intent_script:
  yandex_default:  # это скрипт по умолчанию
    action:  # действие опционально и должно уложиться в пару секунд
    - service: persistent_notification.create
      data:
        title: Команда из Яндекса
        message: "{{ text }}"
    speech:  # фраза для ответа, поддерживает шаблоны
      text: >-
        {% if text == 'привет' %}
          {{ ['слушаю', 'здесь я', 'на связи']|random }}
        {% elif text == 'какая температура в спальне' %}
          Температура {{ states("sensor.temperature_bedroom")|round }} °C
        {% else %}
          Не могу выполнить: {{ text }}
        {% endif %}

  calc:  # это Интент калькулятора (пример как настроить ниже)
    action:
    - service: persistent_notification.create
      data:
        title: Яндекс Калькулятор
        message: "{{ text }}"
    speech:  # в нём распознались переменные action, x и y
      text: >-
        {% if action == 'плюс' %}
          {{ x+y }}
        {% elif action == 'минус' %}
          {{ x-y }}
        {% elif action == 'умножить на' %}
          {{ x*y }}
        {% elif action == 'разделить на' %}
          {{ x/y }}
        {% endif %}

  temperature:  # это Интент температуры в помещении (пример как настроить ниже)
    speech:
      text: >-
        {% if room == 'в зале' %}
          Температура в зале {{ states("sensor.temperature_hall")|round }} °C
        {% elif room == 'в ванной' %}
          Температура в ванной {{ states("sensor.temperature_bathroom")|round }} °C
        {% elif room == 'на балконе' %}
          Температура на балконе {{ states("sensor.temperature_balcony")|round }} °C
        {% endif %}

Управление через python-скрипт

  • Python-код можно писать во внешнем файле или сразу в YAML
  • Код из внешнего файла загружается при каждом вызове диалога (его можно закешировать при старте Hass)
  • Можно установить внешние зависимости python через pip
  • Синтаксис скрипта совместим с функциями Yandex Cloud
yandex_dialogs:
  requirements:     # можно установить внешние библиотеки python
    - requests
  file: dialogs.py  # можно писать код в внемнем файле
  cache: True       # закешировать код внешнего файла (по умолчанию выключено)
  source: |
    def handler(event, context):
        return {"response": {"text": "OK"}, "version": "1.0"}

Пример скрипта (подробнее в документации):

def make_response(event: dict, text: str, end_session=False) -> dict:
    return {
        "version": event["version"],
        "session": event["session"],
        "response": {"text": text, "end_session": end_session},
    }

def handler(event: dict, context: dict) -> dict:
    return make_response(event, "Ну привет!")

Настройка Интентов в Яндекс.Диалогах

Платформа Яндекс.Диалогов позволяет гибко обрабатывать сказанные фразы через Natural Language Processing (NLU) от Яндекса.

Фраза, которую нужно распознать, называется Интентом. ID интента и все составляющие разобранной фразы прилетят в Home Assistant. И их можно использовать в автоматизациях.

При желании вы можете не пользоваться Интентами, а анализовать фразы в автоматизациях Home Assistant или Node-RED.

  • $room - слово с долларом это переменная
  • [...] - квадрытные скобки означают, что слова могут идти в любом порядке
  • (какая)? - вопрос означает, что слова может не быть
  • %lemma - означает режим сравнение без учёта формы слова (например "включай свет" приравнивается к "включи свет")

Полная документация.

Интенты можно настраивать только после публикации навыка. Любые изменения в интентах требуют новой публикации (занимает пару минут).

Пример калькулятора

root:
    сколько будет $x $action $y
slots:
    x:
        source: $x
        type: YANDEX.NUMBER
    y:
        source: $y
        type: YANDEX.NUMBER
    action:
        source: $action
$x:
    $YANDEX.NUMBER
$y:
    $YANDEX.NUMBER
$action:
    плюс | минус | умножить на | разделить на

Пример тепературы в разных помещениях

root:
    [(какая)? температура $room]
    [сколько градусов $room]
slots:
    room:
        source: $room
$room:
    в зале | в ванной | на балконе

yandexdialogs's People

Contributors

alexxit avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

yandexdialogs's Issues

Алиса говорит "Привет я тестовый навык"

После выполнения скрипта

script:
  yandex_dialog1:
    sequence:
      - service: media_player.play_media
        entity_id: media_player.yandex_station # поменяйте на вашу колонку
        data:
          media_content_id: <speaker is_whisper="true">Хозяин, уже утро, пора вставать!
          media_content_type: dialog:тестовый диалог:утро  # имя диалога и "тег" диалога

Алиса просто произносит на колонке "Привет, я тестовый навык" и все, в чем может быть проблема?

Ошибка при проверке навыка

image

При создании диалога, через ADD ENTRY в ИНТЕГРАЦИИ, выдает ошибку "1. Недопустимый ответ ссылка на диалог". При этом, сам диалог создается в Яндекс.Диалогах.

Yandex Dialogs integration crashes every few days

I have a problem. Integration constantly stops working every few days. To restore the functionality, I remove the integration from the home assistant, delete the dialog from Yandex dialogs, then reinstall the integration and create a new dialog. Only after that everything works correctly. Restarting the home assistant and only reinstalling the integration does not restore performance. What could be the problem?

Вывод всей информации из ответа Яндекса в режиме ивентов

В режиме ивентов компонент отправляет в HA ивент с очень куцей информацией о произнесенном:

{
    "command": "9 часов 45 минут",
    "text": "9 часов 45 минут"
}

Хотя API яндекса выдает намного больше информации, например, время в структурированном виде, как в этом примере:

{
    "command": "9 часов 45 минут",
    "markup": {
        "dangerous_context": false
    },
    "nlu": {
        "entities": [
            {
                "tokens": {
                    "end": 2,
                    "start": 0
                },
                "type": "YANDEX.DATETIME",
                "value": {
                    "hour": 9,
                    "hour_is_relative": false,
                    "minute": 45,
                    "minute_is_relative": false
                }
            }
        ],
        "intents": {},
        "tokens": [
            "9",
            "45"
        ]
    },
    "original_utterance": "9 часов 45 минут",
    "type": "SimpleUtterance"
}

Было бы здорово, если бы компонент выдавал всю информацию из ответа API. Насколько я понимаю, для этого нужно просто изменить вот эту строку с

self.hass.bus.async_fire('yandex_intent', slots)

на

self.hass.bus.async_fire('yandex_intent', request)

Если нужно, могу сделать PR.

Приватный навык автоматически не создаётся

установлена последняя версия hassio на hass os. есть действующий сертификат let's. НА доступен извне по 443 порту. Установлена и работает интеграция YandexStation. Установил диалоги. Перезапускаю НА, в уведомлениях нет никакого ид. более того в логах ошибки:

Empty Yandex Login Data

и следом

Received message for unregistered webhook

никакой навык соответственно не создается и ничего не работает.

Открываю вручную путь https://my.ha.com/api/yandex_dialogs
он мне пишет "method is not allowed"

Доступ к переменной session_id

Для того чтобы делать многошаговое взаимодействие необходимо понимать контекст в котором находится пользователь,
например мы спросили через станцию вопрос, получили ответ и мы тогда должны знать на какой вопрос ответил пользователь. Для этого как я понимаю яндекс использует переменную session_id в запросе который прилетает на HA.
"session": {
"message_id": 1,
"session_id": "xxx",
....
Используя session_id мы могли бы сохранять контекст диалога в HA, например через mqtt publish. Было бы круто иметь доступ к этой переменной session_id через trigger.data

фича новой версии 1.3.0

У меня навык был настроен, но после обновления появилось окно - New devices discovered.
Я даже перенастроил навык - добавил новый, удалил старый.
Но данное окно опять появляется.
Не фатально, но немного нервирует.
проверьте пожалуйста.

автоматизации внутри диалога

Добрый день! спасибо за интереснейший компонент.
Вопрос: как вставить автоматизацию внутрь диалогов? (интересует синтаксис)
У меня при попытке добавления стандартных элементов типа trigger, action в тело диалога система выдает ошибку.
Получается вызывать автоматизации лишь по уникальному интенту из компонента Yandex.Station, но в ряде случаев это неудобно. Пример:
Свет в комнате гаснет, когда датчик не видит движения, но допустим человек сидит неподвижно и отключение будет нежелательным. Автоматизация сначала вызывает диалог и Алиса спрашивает: "отключить свет?"
если "нет ответа" или "да" = отключаем свет
иначе = перезапускаем автоматизацию наблюдения за датчиком движения
Подскажите, как реализовать.

Создание и подключение навыка

Так и не смог разобраться.
HA запущен в docker на Synology. ХА проброшен в локальную сеть. Для ХА создан домен на duckdns и он открывается как извне, так и изнутри по внешнему адресу и порту. Порт изменен на нестандартный, но это не имеет значения - проверял с обоими. Настроен и работает сертификат от Let's encrypt. В configuration.yaml прописан host с ssl.
На ХА стоит оба компонента: Yandex.Stattion и Яндекс.Диалоги. Первый работает без проблем.
К сожалению, настроить подключение Яндекс.Диалогов по инструкции не удается, поскольку этот компонент не видит авторизации вторым компонентом (Yandex.Station) - файла, который необходим, в системе нету.. Пробовал вручную создать навык в Яндексе со ссылкой на компонент вида:
https://*.duckdns.org:38123/api/yandex_dialogs
но в ответ Яндекс при попытке опубликовать диалог пишет:

Ошибки валидации:
— Endpoint URL: Ошибка реализации протокола умного дома:
availability: Not Found
devices: Not Found
devices_action: Not Found
devices_query: Not Found
user_unlink: Not Found

со стороны навыков есть еще настройки авторизации с такими данными:
image
Куда еще копать?

Webhook не ответил за отведенное время

При создании диалога вылезла ошибка, тем не менее диалог создался как черновик, я его опубликовал
Доступ извне реализован через dataplicity и работает, но при переходе по ссылке https://уникальная_ссылка.dataplicity.io/api/yandex_dialogs получаю 405: Method Not Allowed
тож самое если перехожу по внутренней ссылке http://homeassistant.local:8123/api/yandex_dialogs

при тестировании в яндекс диалогах ошибка 1. Webhook не ответил за отведенное время

Response:
empty response
Request:
{
  "meta": {
    "locale": "ru-RU",
    "timezone": "UTC",
    "client_id": "ru.yandex.searchplugin/7.16 (none none; android 4.4.2)",
    "interfaces": {
      "screen": {},
      "payments": {},
      "account_linking": {}
    }
  },
  "session": {
    "message_id": 0,
    "session_id": "3b71ca6f-4359-486b-a499-*****",
    "skill_id": "*****-2c61-45e7-97c7-1bc15fede17d",
    "user": {
      "user_id": "F8353B26C9F5*****52BEED4487DF0B861184063BCEEF079EF3A*****"
    },
    "application": {
      "application_id": "962C8C26F5848*****3F49E3E8DD52BC1003C3C3*****948F3E22495654*****"
    },
    "new": true,
    "user_id": "962C8C26F58480FB3C7223F4*****52BC1003C3C3948F3E224956547*****"
  },
  "request": {
    "command": "привет",
    "original_utterance": "привет",
    "nlu": {
      "tokens": [
        "привет"
      ],
      "entities": [],
      "intents": {}
    },
    "markup": {
      "dangerous_context": false
    },
    "type": "SimpleUtterance"
  },
  "state": {
    "session": {},
    "user": {},
    "application": {}
  },
  "version": "1.0"
}
 

Извините, диалог не отвечает

Это то, чего не хватало и давно ждал!
Навык создался автоматом, после перезагрузки HA появилась ссылка на него в уведомлениях, но при попытке его активировать Алиса отвечает постоянно: "Извините, диалог не отвечает". В консоли диалогов получаю ошибку:

  1. HTTP ошибка в ответе webhook: 400. Что еще нужно настроить?

New devices discovered

Каждый раз после перезагрузки Hassio обнаруживает новое устройство - Яндекс диалоги.
В уведомлениях появляется +1.
При этом у меня уже есть настроенная и работающая интеграция Яндекс диалогов.
При попытки создания нового навыка на то же имя выдает, что навык уже существует.
Не могу даже выбрать ее игнорирование.

enhancement: Публичный диалог

Интеграция, конечно, крутая. Очень удобно с её помощью делать навыки не только для умного дома, но и для общего использования.

Отсюда вытекает предложение - было бы, как мне кажется, очень здорово, если была бы возможность не указывать user_id, а принимать любые значения (в случае, если навык публичный)

Снимок экрана 2022-04-15 в 10 44 59

Как запустить триггер, используя вариативный набор слов в событиях?

Как указать в событиях варианты текста, которые нужно перехватить из события? К примеру в событиях могут быть разные слова. Допустим я говорю, расскажи про ..... или что там .... или как там .... и любые слова должны запускать триггер.

событие

Ошибка при создании диалога

Установил через HACS, сделал запись в конфиге.

При перезагрузке вылезает такая ошибка:

Create Skill
Traceback (most recent call last):
  File "/config/custom_components/yandex_dialogs/utils.py", line 49, in create_dialog
    assert r.status == 200, await r.read()
AssertionError: b'{"error":{"message":"Forbidden (no credentials)","code":403}}'

Поможете?

HTTP ошибка в ответе webhook: 500

HA установлен через docker. Yandex Station работает отлично. Но не получается заставить работать диалоги.
При попытке указать данные в Yandex Dialogs получаю ошибку:
Ошибка при проверке навыка:
HTTP ошибка в ответе webhook: 500

При этом навык создается в черновики, но не публикуется. Если опубликовать вручную, то Алиса говорит, мол навык не отвечает.
https://XXX.duckdns.org/api/yandex_dialogs -> 405: Method Not Allowed
В настройках Yandex Dialogs пусто, нет id.

Не могу понять, что делаю не так.

Интеграция настраивается только один раз

Если при первоначальной настройке интеграции нажать отмена, в дальнейшем настроить интеграцию невозможно.

При настройке конфигурации выдается только настройка имени пользователя.
При добавлении интеграции сразу выдается сообщение что интеграция добавлена.

К настройкам можено вернуться, только после удаления интеграции в HACS и повторной установки.
Оказывается эта ошибка воспроизводилась тут

Ошибка "1. Недопустимый ответ"

Навык на ha отрабатывет, автоматизации срабатывают, но до яндекс диалоги ответ доходит как пустой json. Пробовал отправить json на ha из внешней сети, ответ приходит. В чем может быть причина?
Screenshot_20230928_153225_com opera browser
Uploading Screenshot_20230928_153157_com.opera.browser.jpg…

Событие yandex_intent триггерится с неверным интентом

есть кастомный навык и интент для него:

# tv_search
root:
    %lemma
    [найди на телевизоре $room $text]
    [поищи на телевизоре $room $text]

slots:
    room:
        type: Room
        source: $room               
    text:
        source: $text

$room:
    $Room

$text:
    .+

в результате посылается запрос вида:

Request:
{
  "request": {
    "command": "найти на телевизоре в гостиной аватар",
    "original_utterance": "найти на телевизоре в гостиной аватар",
    "nlu": {
      "tokens": [
        "найти",
        "на",
        "телевизоре",
        "в",
        "гостиной",
        "аватар"
      ],
      "entities": [],
      "intents": {
        "YANDEX.BOOK.SEARCH": {
          "slots": {
            "book": {
              "type": "string",
              "tokens": {
                "start": 1,
                "end": 6
              },
              "value": "на телевизоре в гостиной аватар"
            }
          }
        },
        "tv_search": {
          "slots": {
            "text": {
              "type": "YANDEX.STRING",
              "tokens": {
                "start": 5,
                "end": 6
              },
              "value": "аватар"
            },
            "room": {
              "type": "Room",
              "tokens": {
                "start": 3,
                "end": 5
              },
              "value": "livingroom"
            }
          }
        }
      }

но в HA генерится событие только с первым интентом (не моим):

event_type: yandex_intent
data:
  text: найди на телевизоре в гостиной аватар
  command: найди на телевизоре в гостиной аватар
  intent: YANDEX.BOOK.SEARCH
  book: на телевизоре в гостиной аватар
origin: LOCAL
time_fired: "2023-02-18T19:15:48.225861+00:00"
context:
  id: 01GSJZ8CW12AWWDJ8RTK3XQRP1
  parent_id: null
  user_id: null

проблема в этой строчке:

event_data['intent'] = intent_type = next(iter(intents))

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.