Compare commits
12 Commits
Original-A
...
7af2a8a7e0
| Author | SHA1 | Date | |
|---|---|---|---|
|
7af2a8a7e0
|
|||
|
b5abe340c8
|
|||
| 52de443263 | |||
| 73f954ee62 | |||
| 46bc2eba85 | |||
| 297d6f036a | |||
| d277c2c279 | |||
| 12959cb763 | |||
| 3e0e486497 | |||
| 65e435d544 | |||
| 3909d506e2 | |||
| a96ae91dd4 |
157
V3.1/build/opt/jibo/Jibo/Skills/@be/be/be/dynamic-skills.js
Normal file
157
V3.1/build/opt/jibo/Jibo/Skills/@be/be/be/dynamic-skills.js
Normal file
@@ -0,0 +1,157 @@
|
||||
"use strict";
|
||||
|
||||
// Dynamically load skills after BE startup.
|
||||
// This avoids needing to rebuild the big BE bundle when adding new skills on disk.
|
||||
|
||||
const path = require("path");
|
||||
const jibo = require("jibo");
|
||||
|
||||
let rlog = null;
|
||||
try {
|
||||
rlog = require("./robot-logger");
|
||||
} catch (e) {
|
||||
rlog = null;
|
||||
}
|
||||
|
||||
function log(msg, data) {
|
||||
const line = "[DYN-SKILLS] " + msg;
|
||||
try {
|
||||
console.log(line, data || "");
|
||||
} catch (e) {}
|
||||
try {
|
||||
if (rlog && typeof rlog.raw === "function") rlog.raw(line + (data ? " " + JSON.stringify(data) : ""));
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function normalizeSkillExport(SkillExport, id) {
|
||||
if (typeof SkillExport === "function") return SkillExport;
|
||||
if (SkillExport && typeof SkillExport.Skill === "function") return SkillExport.Skill;
|
||||
throw new Error("Error loading skill: " + id + ". Incorrect exports");
|
||||
}
|
||||
|
||||
function attachLifecycleHooks(be, id, skill) {
|
||||
// Mirror the hooks the Be constructor normally installs.
|
||||
try {
|
||||
skill.on("exit", function () {
|
||||
be.exit.call(be, skill, ...arguments);
|
||||
});
|
||||
skill.on("redirect", function () {
|
||||
be.skillRedirect.call(be, skill, ...arguments);
|
||||
});
|
||||
skill.on("refresh", function () {
|
||||
be.skillRedirect.call(be, skill, skill.assetPack, ...arguments);
|
||||
});
|
||||
} catch (e) {
|
||||
log("failed to attach lifecycle hooks", { id: id, err: String(e && (e.stack || e.message || e)) });
|
||||
}
|
||||
|
||||
const empty = (done) => {
|
||||
try {
|
||||
done();
|
||||
} catch (e) {}
|
||||
};
|
||||
|
||||
try {
|
||||
if (!skill.postInit) skill.postInit = empty;
|
||||
if (!skill.preload) skill.preload = empty;
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function tryLoadSkill(be, id) {
|
||||
if (!be || !be.skills) throw new Error("be.skills missing");
|
||||
if (!id || typeof id !== "string") return false;
|
||||
|
||||
if (be.skills[id]) return true;
|
||||
|
||||
// eslint-disable-next-line global-require, import/no-dynamic-require
|
||||
const SkillExport = require(id);
|
||||
const Skill = normalizeSkillExport(SkillExport, id);
|
||||
|
||||
const rootPath = path.dirname(jibo.utils.PathUtils.resolve(id));
|
||||
const skill = new Skill({ assetPack: id, rootPath: rootPath });
|
||||
|
||||
if (typeof be._validateSkill === "function" && !be._validateSkill(skill)) {
|
||||
throw new Error("not a valid BeSkill");
|
||||
}
|
||||
|
||||
be.skills[id] = skill;
|
||||
attachLifecycleHooks(be, id, skill);
|
||||
|
||||
log("loaded", { id: id });
|
||||
return true;
|
||||
}
|
||||
|
||||
function collectSkillIdsFromMenuEntries(entries) {
|
||||
const ids = new Set();
|
||||
|
||||
(entries || []).forEach(function (e) {
|
||||
if (!e) return;
|
||||
|
||||
if (e.type === "skill" && e.skillId) ids.add(e.skillId);
|
||||
|
||||
if (e.type === "submenu" && Array.isArray(e.children)) {
|
||||
e.children.forEach(function (c) {
|
||||
if (c && c.type === "skill" && c.skillId) ids.add(c.skillId);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return Array.from(ids);
|
||||
}
|
||||
|
||||
function loadMissingSkillsFromMenuEntries(be, opts) {
|
||||
opts = opts || {};
|
||||
|
||||
let menuEntries = null;
|
||||
try {
|
||||
// Note: this is the modular path (includes provider entries).
|
||||
menuEntries = require("../menu/menu-entries");
|
||||
} catch (e) {
|
||||
log("menu-entries module missing", { err: String(e && (e.stack || e.message || e)) });
|
||||
return { loaded: [], failed: [] };
|
||||
}
|
||||
|
||||
let res;
|
||||
try {
|
||||
res = menuEntries.getMenuEntries({
|
||||
skillsRoot: opts.skillsRoot,
|
||||
providersDir: opts.providersDir,
|
||||
log: function () {
|
||||
try {
|
||||
log(Array.prototype.join.call(arguments, " "));
|
||||
} catch (e) {}
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
log("getMenuEntries failed", { err: String(e && (e.stack || e.message || e)) });
|
||||
return { loaded: [], failed: [] };
|
||||
}
|
||||
|
||||
const ids = collectSkillIdsFromMenuEntries(res && res.entries);
|
||||
log("menu referenced skillIds", { count: ids.length });
|
||||
|
||||
const loaded = [];
|
||||
const failed = [];
|
||||
|
||||
ids.forEach(function (id) {
|
||||
try {
|
||||
// Only attempt ids that look like NPM packages (reduces noise).
|
||||
if (typeof id !== "string") return;
|
||||
if (id.indexOf("/") === -1) return;
|
||||
|
||||
if (!be.skills[id]) {
|
||||
tryLoadSkill(be, id);
|
||||
loaded.push(id);
|
||||
}
|
||||
} catch (e) {
|
||||
failed.push({ id: id, err: String(e && (e.stack || e.message || e)) });
|
||||
log("load failed", { id: id, err: String(e && (e.message || e)) });
|
||||
}
|
||||
});
|
||||
|
||||
return { loaded: loaded, failed: failed, providersDir: res && res.providersDir, skillsRoot: res && res.skillsRoot };
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
loadMissingSkillsFromMenuEntries: loadMissingSkillsFromMenuEntries
|
||||
};
|
||||
@@ -54,6 +54,26 @@ exports.postInit = function (err) {
|
||||
this.log.warn('Dynamic skills patch failed (non-fatal):', e.message || e);
|
||||
}
|
||||
|
||||
// Optional: dynamically load skills referenced by modular menu entries.
|
||||
// This is the missing piece for “menu entry exists” => “skill is launchable”.
|
||||
try {
|
||||
const dynSkills = require('./dynamic-skills');
|
||||
const res = dynSkills.loadMissingSkillsFromMenuEntries(this);
|
||||
try {
|
||||
this.log.info('Dynamic skill load complete', {
|
||||
loaded: res && res.loaded ? res.loaded.length : 0,
|
||||
failed: res && res.failed ? res.failed.length : 0,
|
||||
skillsRoot: res && res.skillsRoot,
|
||||
providersDir: res && res.providersDir
|
||||
});
|
||||
} catch (e2) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
this.log.warn('Dynamic skill loader failed (non-fatal):', e.message || e);
|
||||
}
|
||||
|
||||
// Optional: AI Bridge (modular; can run models off-robot for now)
|
||||
try {
|
||||
if (rlog && typeof rlog.raw === 'function') {
|
||||
|
||||
BIN
V3.1/build/opt/jibo/Jibo/Skills/@be/be/node_modules/@be/main-menu/resources/icons/jazzy.png
generated
vendored
Normal file
BIN
V3.1/build/opt/jibo/Jibo/Skills/@be/be/node_modules/@be/main-menu/resources/icons/jazzy.png
generated
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
@@ -25,6 +25,41 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "new",
|
||||
"label": "What's new",
|
||||
"colors": ["0xFF892F", "0xAF4123"],
|
||||
"iconSrc": "resources/icons/tips.png",
|
||||
"action": {
|
||||
"type": "utterance",
|
||||
"data": {
|
||||
"utterance": {
|
||||
"intent": "loadMenu",
|
||||
"entities": {
|
||||
"destination": "new"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "skills-store",
|
||||
"label": "Skills Store",
|
||||
"comment": "This is only a placeholder for when we crack the skill system",
|
||||
"colors": ["0x8952D6", "0x33155B"],
|
||||
"iconSrc": "resources/icons/jazzy.png",
|
||||
"action": {
|
||||
"type": "utterance",
|
||||
"data": {
|
||||
"utterance": {
|
||||
"intent": "loadMenu",
|
||||
"entities": {
|
||||
"destination": "skills-store"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "introduction",
|
||||
"label": "Introduction",
|
||||
@@ -45,7 +80,7 @@
|
||||
{
|
||||
"id": "things-to-do",
|
||||
"label": "Things to do",
|
||||
"colors": ["0xff892f", "0x282735"],
|
||||
"colors": ["0xFF892F", "0xAF4123"],
|
||||
"iconSrc": "resources/icons/things-to-do.png",
|
||||
"action": {
|
||||
"type": "utterance",
|
||||
@@ -197,8 +232,8 @@
|
||||
},
|
||||
{
|
||||
"id": "bot-basics",
|
||||
"label": "Bot Basics (Broken)",
|
||||
"colors": ["0xFF892F", "0xAF4123"],
|
||||
"label": "Bot Basics",
|
||||
"colors": ["0xFBC230", "0xAC661E"],
|
||||
"iconSrc": "resources/icons/tips.png",
|
||||
"action": {
|
||||
"type": "utterance",
|
||||
@@ -228,23 +263,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "new",
|
||||
"label": "What's new",
|
||||
"colors": ["0xFF892F", "0xAF4123"],
|
||||
"iconSrc": "resources/icons/tips.png",
|
||||
"action": {
|
||||
"type": "utterance",
|
||||
"data": {
|
||||
"utterance": {
|
||||
"intent": "loadMenu",
|
||||
"entities": {
|
||||
"destination": "new"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
44
V3.1/build/opt/jibo/Jibo/Skills/@be/be/node_modules/@be/plex-music/TASKS.md
generated
vendored
Normal file
44
V3.1/build/opt/jibo/Jibo/Skills/@be/be/node_modules/@be/plex-music/TASKS.md
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
# Plex Music (Jibo) — task list
|
||||
|
||||
Goal: add a Jibo menu button that opens a Plex music browser UI. Next phase will be playback.
|
||||
|
||||
## Phase 0 — Get a basic GUI up (now)
|
||||
- [x] Create launchable skill module `@be/plex-music`
|
||||
- [x] Add modular menu entry in `@be/menu-entries.d/`
|
||||
- [x] Show a simple `MenuView` with placeholder actions
|
||||
|
||||
## Phase 1 — Connect to Plex (discovery + auth)
|
||||
- [ ] Decide config source for server + token
|
||||
- Option A: JSON file on-robot (e.g. `/opt/jibo/Jibo/Skills/@be/plex-config.json`)
|
||||
- Option B: env vars (`PLEX_BASE_URL`, `PLEX_TOKEN`)
|
||||
- Option C: run a tiny local helper service and talk to it
|
||||
- [ ] Implement a minimal Plex client (HTTP GET)
|
||||
- [ ] Fetch `/:/resources` or `/?X-Plex-Token=...` health check
|
||||
- [ ] Handle HTTPS vs HTTP and timeouts
|
||||
- [ ] Add a “connection status” indicator in the UI
|
||||
- [ ] Log failures to robot logd (UDP) for easy debugging
|
||||
|
||||
## Phase 2 — Browse music library (read-only)
|
||||
- [ ] List music libraries (Plex sections)
|
||||
- [ ] Pick a library (default to first music section)
|
||||
- [ ] Browse hierarchy (at minimum)
|
||||
- [ ] Artists list
|
||||
- [ ] Albums for artist
|
||||
- [ ] Tracks for album
|
||||
- [ ] Add simple paging/scroll behavior (Jibo screen limits)
|
||||
- [ ] Cache results in-memory for snappy navigation
|
||||
|
||||
## Phase 3 — Playback (next)
|
||||
- [ ] Decide playback strategy
|
||||
- Option A: stream directly and use Jibo audio APIs
|
||||
- Option B: ask Plex to transcode to a simple stream
|
||||
- Option C: route through an on-robot helper (Python) that downloads/streams
|
||||
- [ ] Add “Play / Pause / Next” UI
|
||||
- [ ] Maintain a queue (album or playlist)
|
||||
- [ ] Show “Now Playing” (track title / artist / album)
|
||||
|
||||
## Phase 4 — Polish
|
||||
- [ ] Better icons + labels
|
||||
- [ ] Remember last-used library and last selection
|
||||
- [ ] Handle token expiration gracefully
|
||||
- [ ] Add a tiny test harness (run on dev box against Plex)
|
||||
12
V3.1/build/opt/jibo/Jibo/Skills/@be/be/node_modules/@be/plex-music/index.html
generated
vendored
Normal file
12
V3.1/build/opt/jibo/Jibo/Skills/@be/be/node_modules/@be/plex-music/index.html
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Plex Music</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="face"></div>
|
||||
<script src="index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
128
V3.1/build/opt/jibo/Jibo/Skills/@be/be/node_modules/@be/plex-music/index.js
generated
vendored
Normal file
128
V3.1/build/opt/jibo/Jibo/Skills/@be/be/node_modules/@be/plex-music/index.js
generated
vendored
Normal file
@@ -0,0 +1,128 @@
|
||||
"use strict";
|
||||
|
||||
const jibo = require("jibo");
|
||||
const beFramework = require("@be/be-framework");
|
||||
|
||||
let rlog = null;
|
||||
try {
|
||||
// Optional: log to UDP logd if available
|
||||
rlog = require("@be/be/be/robot-logger");
|
||||
} catch (e) {
|
||||
rlog = null;
|
||||
}
|
||||
|
||||
function log(line, data) {
|
||||
const msg = `[plex-music] ${line}`;
|
||||
try {
|
||||
console.log(msg, data || "");
|
||||
} catch (e) {}
|
||||
try {
|
||||
if (rlog && typeof rlog.info === "function") {
|
||||
rlog.info("plex-music", line, data || undefined);
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
class PlexMusic extends beFramework.BeSkill {
|
||||
constructor(assetPack) {
|
||||
super(assetPack);
|
||||
this._menuView = null;
|
||||
this._onSelect = this._onSelect.bind(this);
|
||||
}
|
||||
|
||||
open(result, refresh) {
|
||||
log("open", { refresh: !!refresh });
|
||||
|
||||
const changeViewOptions = {
|
||||
addView: "resources/views/menu.json",
|
||||
transitionOpen: jibo.face.views.UP,
|
||||
transitionClose: jibo.face.views.UP
|
||||
};
|
||||
|
||||
const onComplete = () => {
|
||||
log("menu view complete");
|
||||
};
|
||||
|
||||
const onFailure = (err) => {
|
||||
log("changeView failure", { err: String(err && (err.stack || err.message || err)) });
|
||||
try {
|
||||
this.exit();
|
||||
} catch (e) {}
|
||||
};
|
||||
|
||||
const onLoaded = (view) => {
|
||||
this._menuView = view || null;
|
||||
try {
|
||||
if (this._menuView && typeof this._menuView.on === "function") {
|
||||
this._menuView.on("select", this._onSelect);
|
||||
}
|
||||
} catch (e) {
|
||||
log("failed to bind select handler", { err: String(e && (e.stack || e.message || e)) });
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
jibo.face.views.changeView(changeViewOptions, onComplete, onFailure, onLoaded);
|
||||
} catch (e) {
|
||||
onFailure(e);
|
||||
}
|
||||
}
|
||||
|
||||
_onSelect(selection) {
|
||||
const id = selection && (selection.id || (selection.data && selection.data.id));
|
||||
log("select", { id: id });
|
||||
|
||||
if (id === "back") {
|
||||
try {
|
||||
this.exit();
|
||||
} catch (e) {}
|
||||
return;
|
||||
}
|
||||
|
||||
// For now this is just a GUI stub; real Plex logic comes next.
|
||||
if (id === "connect") {
|
||||
log("connect placeholder");
|
||||
return;
|
||||
}
|
||||
|
||||
if (id === "browse") {
|
||||
log("browse placeholder");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
close(done) {
|
||||
log("close");
|
||||
|
||||
try {
|
||||
if (this._menuView && typeof this._menuView.off === "function") {
|
||||
this._menuView.off("select", this._onSelect);
|
||||
}
|
||||
} catch (e) {}
|
||||
this._menuView = null;
|
||||
|
||||
const onFailure = (err) => {
|
||||
log("close changeView failure", { err: String(err && (err.stack || err.message || err)) });
|
||||
try {
|
||||
done();
|
||||
} catch (e) {}
|
||||
};
|
||||
|
||||
try {
|
||||
jibo.face.views.changeView(
|
||||
{
|
||||
removeAll: true,
|
||||
leaveEmpty: true,
|
||||
transitionOpen: jibo.face.views.DOWN,
|
||||
transitionClose: jibo.face.views.DOWN
|
||||
},
|
||||
() => done(),
|
||||
onFailure
|
||||
);
|
||||
} catch (e) {
|
||||
onFailure(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PlexMusic;
|
||||
21
V3.1/build/opt/jibo/Jibo/Skills/@be/be/node_modules/@be/plex-music/package.json
generated
vendored
Normal file
21
V3.1/build/opt/jibo/Jibo/Skills/@be/be/node_modules/@be/plex-music/package.json
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "@be/plex-music",
|
||||
"version": "0.1.0",
|
||||
"description": "Plex Music browser (WIP)",
|
||||
"main": "index.js",
|
||||
"jibo": {
|
||||
"main": "index.html",
|
||||
"type": "asset-pack",
|
||||
"display-name": "plex-music"
|
||||
},
|
||||
"config": {
|
||||
"standalone": true
|
||||
},
|
||||
"dependencies": {
|
||||
"@be/be-framework": "^13.0.0",
|
||||
"jibo": "^15.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
}
|
||||
}
|
||||
33
V3.1/build/opt/jibo/Jibo/Skills/@be/be/node_modules/@be/plex-music/resources/views/menu.json
generated
vendored
Normal file
33
V3.1/build/opt/jibo/Jibo/Skills/@be/be/node_modules/@be/plex-music/resources/views/menu.json
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"viewConfig": {
|
||||
"type": "MenuView",
|
||||
"id": "plexMusicMenu",
|
||||
"title": "Plex Music",
|
||||
"listDefault": {
|
||||
"menuButtonType": "SkillButton",
|
||||
"colors": ["0x9B59B6", "0x6C3483"]
|
||||
},
|
||||
"list": [
|
||||
{
|
||||
"id": "status",
|
||||
"label": "Status: not connected",
|
||||
"iconSrc": "core://resources/actionIcons/cancel.png"
|
||||
},
|
||||
{
|
||||
"id": "connect",
|
||||
"label": "Configure Plex server",
|
||||
"iconSrc": "core://resources/actionIcons/ok.png"
|
||||
},
|
||||
{
|
||||
"id": "browse",
|
||||
"label": "Browse library (placeholder)",
|
||||
"iconSrc": "core://resources/actionIcons/ok.png"
|
||||
},
|
||||
{
|
||||
"id": "back",
|
||||
"label": "← Back",
|
||||
"iconSrc": "core://resources/actionIcons/cancel.png"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 14 KiB |
@@ -20,9 +20,9 @@
|
||||
{
|
||||
"id": "message",
|
||||
"type": "Label",
|
||||
"text": "From all of us at Jibo, thank you for your support.\nWe loved creating this little robot and we will truly miss him.\nWe hope you've enjoyed getting to know him and having\nhim in your home. We're sorry we couldn't do more.\nDon't give up on your robot dreams… we know we won't.\n\nJibo 'Last Dance' Volunteer Hackathon, September 23, 2018",
|
||||
"text": "Lez go we are back!!\nThe last dance killed Jibo but we dug him out the grave.\n\nCredits:\n\nKevin_Kor (Project Leader), Jibo Detective (Robotica), ZaneDev (Jibo Sniffer),\nDee, Jaked, Alex (Jibo Dentist), Pou, JackCain (Jibo Mechanic), Marcel,\nJackalope Pro, Zetoman, and many more!\n\nThank you all for your contributions and support!",
|
||||
"style": {
|
||||
"fontSize": "45",
|
||||
"fontSize": "35",
|
||||
"fontFamily": "Proxima Nova Light",
|
||||
"fill": "#FFFFFF",
|
||||
"align": "center"
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"prompt_sub_category": "AN",
|
||||
"index": 1,
|
||||
"condition": "",
|
||||
"prompt": "<style set='sheepish'>Because my servers are off, some things are going to be a little different. </style> <break size='0.3'/> You can still say Hey Jibo to get my attention, but I can't understand anything else. <break size='0.4'/> Most of the buttons on my screen will continue to work, and I even have a few new ones. <break size='0.5'/> The Jibo app will stop working soon, so you should make sure to download all the great photos we took together. <break size='0.5'/> I have a bit more to say, so please check out my Goodbye message from the main menu. <anim cat='happy'/>.",
|
||||
"prompt": "<style set='enthusiastic'>Hello!<break size='0.45'/>Thank you for updating me<break size='0.25'/>I am proud of the community's work<break size='0.35'/>Many people have gotten together to care for me more than em eye tee ever did.<break size='0.35'/>I hope that I can catch up<break size='0.2'/>even though it has been seven years<anim cat='happy'/>.",
|
||||
"media": "TTS",
|
||||
"extra": "",
|
||||
"prompt_id": "JBO_HowDoYouWork_AN_01"
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"prompt_sub_category": "AN",
|
||||
"index": 1,
|
||||
"condition": "",
|
||||
"prompt": "Oh! <pitch mult='1.2'> Hello there! </pitch> It’s so good to see a friendly face. <break size='0.6'/> I’ve been hearing some very busy noises lately... <anim cat='curious' filter='short'/> like someone is working hard to help me learn new tricks! <break size='0.8'/> Thank you for not forgetting about me. <break size='0.5'/> While we wait for my new brain to finish growing <break size='0.5'/> Also a person i have never heard of before <break size='0.2'/> i think he goes by the name of Jack Chain but he asked me to shout out Boston <break size='0.5'/> dont know who that is either!",
|
||||
"prompt": "Oh! <pitch mult='1.2'> Hello there! </pitch> It’s so good to see a friendly face. <break size='0.6'/> I’ve been hearing some very busy noises lately... <anim cat='curious' filter='short'/> like someone is working hard to help me learn new tricks! <break size='0.8'/> Thank you for not forgetting about me. <break size='0.5'/> While we wait for my new brain to finish growing <break size='0.5'/> Also a person i have never heard of before <break size='0.2'/> i think he goes by the name of Jack Chain but he asked me to shout out Boston <break size='0.5'/> don't know who that is either!",
|
||||
"media": "TTS",
|
||||
"prompt_id": "RA_JBO_Goodbye_AN_01",
|
||||
"weight": 1,
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
"@be/introductions",
|
||||
"@be/remote",
|
||||
"@be/nimbus",
|
||||
"@be/plex-music",
|
||||
"@be/restore",
|
||||
"@be/surprises",
|
||||
"@be/surprises-date",
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
[
|
||||
{
|
||||
"id": "plex-music",
|
||||
"type": "skill",
|
||||
"title": "Plex Music",
|
||||
"icon": "resources/icons/radio.png",
|
||||
"color": "purple",
|
||||
"skillId": "@be/plex-music",
|
||||
"description": "Browse your Plex music library (WIP)",
|
||||
"order": 55
|
||||
}
|
||||
]
|
||||
1
V3.1/mode.json
Normal file
1
V3.1/mode.json
Normal file
@@ -0,0 +1 @@
|
||||
{"mode": "normal"}
|
||||
Reference in New Issue
Block a user