@dxos/plugin-youtube 0.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/LICENSE +21 -0
  2. package/dist/lib/browser/ChannelArticle-CDQR4BBY.mjs +90 -0
  3. package/dist/lib/browser/ChannelArticle-CDQR4BBY.mjs.map +7 -0
  4. package/dist/lib/browser/ChannelSettings-ZYUNW3VS.mjs +28 -0
  5. package/dist/lib/browser/ChannelSettings-ZYUNW3VS.mjs.map +7 -0
  6. package/dist/lib/browser/VideoArticle-FC4A6E7B.mjs +76 -0
  7. package/dist/lib/browser/VideoArticle-FC4A6E7B.mjs.map +7 -0
  8. package/dist/lib/browser/VideoCard-CCPXDCB7.mjs +64 -0
  9. package/dist/lib/browser/VideoCard-CCPXDCB7.mjs.map +7 -0
  10. package/dist/lib/browser/app-graph-builder-MJY6A6SN.mjs +195 -0
  11. package/dist/lib/browser/app-graph-builder-MJY6A6SN.mjs.map +7 -0
  12. package/dist/lib/browser/blueprint-definition-FRYUYJ22.mjs +22 -0
  13. package/dist/lib/browser/blueprint-definition-FRYUYJ22.mjs.map +7 -0
  14. package/dist/lib/browser/blueprints/index.mjs +13 -0
  15. package/dist/lib/browser/blueprints/index.mjs.map +7 -0
  16. package/dist/lib/browser/chunk-C26XKDK2.mjs +355 -0
  17. package/dist/lib/browser/chunk-C26XKDK2.mjs.map +7 -0
  18. package/dist/lib/browser/chunk-DFRSBBSO.mjs +21 -0
  19. package/dist/lib/browser/chunk-DFRSBBSO.mjs.map +7 -0
  20. package/dist/lib/browser/chunk-GFRR4TTX.mjs +72 -0
  21. package/dist/lib/browser/chunk-GFRR4TTX.mjs.map +7 -0
  22. package/dist/lib/browser/chunk-J5LGTIGS.mjs +10 -0
  23. package/dist/lib/browser/chunk-J5LGTIGS.mjs.map +7 -0
  24. package/dist/lib/browser/chunk-MUE22YUM.mjs +57 -0
  25. package/dist/lib/browser/chunk-MUE22YUM.mjs.map +7 -0
  26. package/dist/lib/browser/chunk-P67QEKBQ.mjs +72 -0
  27. package/dist/lib/browser/chunk-P67QEKBQ.mjs.map +7 -0
  28. package/dist/lib/browser/chunk-YMDT37TA.mjs +62 -0
  29. package/dist/lib/browser/chunk-YMDT37TA.mjs.map +7 -0
  30. package/dist/lib/browser/chunk-Z3DGTMKC.mjs +8 -0
  31. package/dist/lib/browser/chunk-Z3DGTMKC.mjs.map +7 -0
  32. package/dist/lib/browser/clear-synced-videos-EVMJIZPD.mjs +66 -0
  33. package/dist/lib/browser/clear-synced-videos-EVMJIZPD.mjs.map +7 -0
  34. package/dist/lib/browser/index.mjs +153 -0
  35. package/dist/lib/browser/index.mjs.map +7 -0
  36. package/dist/lib/browser/meta.json +1 -0
  37. package/dist/lib/browser/react-surface-EDA5VYDC.mjs +77 -0
  38. package/dist/lib/browser/react-surface-EDA5VYDC.mjs.map +7 -0
  39. package/dist/lib/browser/sync-423Q4BDD.mjs +360 -0
  40. package/dist/lib/browser/sync-423Q4BDD.mjs.map +7 -0
  41. package/dist/lib/browser/types/index.mjs +14 -0
  42. package/dist/lib/browser/types/index.mjs.map +7 -0
  43. package/dist/lib/node-esm/ChannelArticle-GQ64BO7V.mjs +91 -0
  44. package/dist/lib/node-esm/ChannelArticle-GQ64BO7V.mjs.map +7 -0
  45. package/dist/lib/node-esm/ChannelSettings-DM2HWNKO.mjs +29 -0
  46. package/dist/lib/node-esm/ChannelSettings-DM2HWNKO.mjs.map +7 -0
  47. package/dist/lib/node-esm/VideoArticle-WLTWZO3K.mjs +77 -0
  48. package/dist/lib/node-esm/VideoArticle-WLTWZO3K.mjs.map +7 -0
  49. package/dist/lib/node-esm/VideoCard-FOWQZK75.mjs +65 -0
  50. package/dist/lib/node-esm/VideoCard-FOWQZK75.mjs.map +7 -0
  51. package/dist/lib/node-esm/app-graph-builder-IU5TBAXN.mjs +196 -0
  52. package/dist/lib/node-esm/app-graph-builder-IU5TBAXN.mjs.map +7 -0
  53. package/dist/lib/node-esm/blueprint-definition-W264MZ3D.mjs +23 -0
  54. package/dist/lib/node-esm/blueprint-definition-W264MZ3D.mjs.map +7 -0
  55. package/dist/lib/node-esm/blueprints/index.mjs +14 -0
  56. package/dist/lib/node-esm/blueprints/index.mjs.map +7 -0
  57. package/dist/lib/node-esm/chunk-5KNC2JMP.mjs +58 -0
  58. package/dist/lib/node-esm/chunk-5KNC2JMP.mjs.map +7 -0
  59. package/dist/lib/node-esm/chunk-6BUJ2DQX.mjs +73 -0
  60. package/dist/lib/node-esm/chunk-6BUJ2DQX.mjs.map +7 -0
  61. package/dist/lib/node-esm/chunk-CX6MV3QM.mjs +23 -0
  62. package/dist/lib/node-esm/chunk-CX6MV3QM.mjs.map +7 -0
  63. package/dist/lib/node-esm/chunk-CZSLL3XQ.mjs +63 -0
  64. package/dist/lib/node-esm/chunk-CZSLL3XQ.mjs.map +7 -0
  65. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +11 -0
  66. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs.map +7 -0
  67. package/dist/lib/node-esm/chunk-JM5SBBP5.mjs +356 -0
  68. package/dist/lib/node-esm/chunk-JM5SBBP5.mjs.map +7 -0
  69. package/dist/lib/node-esm/chunk-JSGRZMG3.mjs +73 -0
  70. package/dist/lib/node-esm/chunk-JSGRZMG3.mjs.map +7 -0
  71. package/dist/lib/node-esm/chunk-M4S6BE47.mjs +10 -0
  72. package/dist/lib/node-esm/chunk-M4S6BE47.mjs.map +7 -0
  73. package/dist/lib/node-esm/clear-synced-videos-5UCH6XHL.mjs +67 -0
  74. package/dist/lib/node-esm/clear-synced-videos-5UCH6XHL.mjs.map +7 -0
  75. package/dist/lib/node-esm/index.mjs +154 -0
  76. package/dist/lib/node-esm/index.mjs.map +7 -0
  77. package/dist/lib/node-esm/meta.json +1 -0
  78. package/dist/lib/node-esm/react-surface-5DJAQPHJ.mjs +78 -0
  79. package/dist/lib/node-esm/react-surface-5DJAQPHJ.mjs.map +7 -0
  80. package/dist/lib/node-esm/sync-CEF5DX2J.mjs +361 -0
  81. package/dist/lib/node-esm/sync-CEF5DX2J.mjs.map +7 -0
  82. package/dist/lib/node-esm/types/index.mjs +15 -0
  83. package/dist/lib/node-esm/types/index.mjs.map +7 -0
  84. package/dist/types/src/YouTubePlugin.d.ts +3 -0
  85. package/dist/types/src/YouTubePlugin.d.ts.map +1 -0
  86. package/dist/types/src/blueprints/index.d.ts +3 -0
  87. package/dist/types/src/blueprints/index.d.ts.map +1 -0
  88. package/dist/types/src/blueprints/youtube.d.ts +4 -0
  89. package/dist/types/src/blueprints/youtube.d.ts.map +1 -0
  90. package/dist/types/src/capabilities/app-graph-builder/app-graph-builder.d.ts +6 -0
  91. package/dist/types/src/capabilities/app-graph-builder/app-graph-builder.d.ts.map +1 -0
  92. package/dist/types/src/capabilities/app-graph-builder/index.d.ts +3 -0
  93. package/dist/types/src/capabilities/app-graph-builder/index.d.ts.map +1 -0
  94. package/dist/types/src/capabilities/blueprint-definition/blueprint-definition.d.ts +6 -0
  95. package/dist/types/src/capabilities/blueprint-definition/blueprint-definition.d.ts.map +1 -0
  96. package/dist/types/src/capabilities/blueprint-definition/index.d.ts +3 -0
  97. package/dist/types/src/capabilities/blueprint-definition/index.d.ts.map +1 -0
  98. package/dist/types/src/capabilities/index.d.ts +4 -0
  99. package/dist/types/src/capabilities/index.d.ts.map +1 -0
  100. package/dist/types/src/capabilities/react-surface/index.d.ts +3 -0
  101. package/dist/types/src/capabilities/react-surface/index.d.ts.map +1 -0
  102. package/dist/types/src/capabilities/react-surface/react-surface.d.ts +5 -0
  103. package/dist/types/src/capabilities/react-surface/react-surface.d.ts.map +1 -0
  104. package/dist/types/src/containers/ChannelArticle/ChannelArticle.d.ts +8 -0
  105. package/dist/types/src/containers/ChannelArticle/ChannelArticle.d.ts.map +1 -0
  106. package/dist/types/src/containers/ChannelArticle/index.d.ts +3 -0
  107. package/dist/types/src/containers/ChannelArticle/index.d.ts.map +1 -0
  108. package/dist/types/src/containers/ChannelSettings/ChannelSettings.d.ts +7 -0
  109. package/dist/types/src/containers/ChannelSettings/ChannelSettings.d.ts.map +1 -0
  110. package/dist/types/src/containers/ChannelSettings/index.d.ts +3 -0
  111. package/dist/types/src/containers/ChannelSettings/index.d.ts.map +1 -0
  112. package/dist/types/src/containers/VideoArticle/VideoArticle.d.ts +11 -0
  113. package/dist/types/src/containers/VideoArticle/VideoArticle.d.ts.map +1 -0
  114. package/dist/types/src/containers/VideoArticle/index.d.ts +3 -0
  115. package/dist/types/src/containers/VideoArticle/index.d.ts.map +1 -0
  116. package/dist/types/src/containers/VideoCard/VideoCard.d.ts +9 -0
  117. package/dist/types/src/containers/VideoCard/VideoCard.d.ts.map +1 -0
  118. package/dist/types/src/containers/VideoCard/index.d.ts +3 -0
  119. package/dist/types/src/containers/VideoCard/index.d.ts.map +1 -0
  120. package/dist/types/src/containers/index.d.ts +11 -0
  121. package/dist/types/src/containers/index.d.ts.map +1 -0
  122. package/dist/types/src/index.d.ts +4 -0
  123. package/dist/types/src/index.d.ts.map +1 -0
  124. package/dist/types/src/meta.d.ts +3 -0
  125. package/dist/types/src/meta.d.ts.map +1 -0
  126. package/dist/types/src/operations/apis/index.d.ts +2 -0
  127. package/dist/types/src/operations/apis/index.d.ts.map +1 -0
  128. package/dist/types/src/operations/apis/youtube/api.d.ts +251 -0
  129. package/dist/types/src/operations/apis/youtube/api.d.ts.map +1 -0
  130. package/dist/types/src/operations/apis/youtube/index.d.ts +3 -0
  131. package/dist/types/src/operations/apis/youtube/index.d.ts.map +1 -0
  132. package/dist/types/src/operations/apis/youtube/types.d.ts +493 -0
  133. package/dist/types/src/operations/apis/youtube/types.d.ts.map +1 -0
  134. package/dist/types/src/operations/clear-synced-videos.d.ts +5 -0
  135. package/dist/types/src/operations/clear-synced-videos.d.ts.map +1 -0
  136. package/dist/types/src/operations/definitions.d.ts +45 -0
  137. package/dist/types/src/operations/definitions.d.ts.map +1 -0
  138. package/dist/types/src/operations/index.d.ts +5 -0
  139. package/dist/types/src/operations/index.d.ts.map +1 -0
  140. package/dist/types/src/operations/services/google-credentials.d.ts +35 -0
  141. package/dist/types/src/operations/services/google-credentials.d.ts.map +1 -0
  142. package/dist/types/src/operations/services/index.d.ts +2 -0
  143. package/dist/types/src/operations/services/index.d.ts.map +1 -0
  144. package/dist/types/src/operations/sync.d.ts +5 -0
  145. package/dist/types/src/operations/sync.d.ts.map +1 -0
  146. package/dist/types/src/operations/sync.test.d.ts +2 -0
  147. package/dist/types/src/operations/sync.test.d.ts.map +1 -0
  148. package/dist/types/src/operations/transcript.d.ts +12 -0
  149. package/dist/types/src/operations/transcript.d.ts.map +1 -0
  150. package/dist/types/src/translations.d.ts +49 -0
  151. package/dist/types/src/translations.d.ts.map +1 -0
  152. package/dist/types/src/types/Channel.d.ts +54 -0
  153. package/dist/types/src/types/Channel.d.ts.map +1 -0
  154. package/dist/types/src/types/Video.d.ts +41 -0
  155. package/dist/types/src/types/Video.d.ts.map +1 -0
  156. package/dist/types/src/types/index.d.ts +4 -0
  157. package/dist/types/src/types/index.d.ts.map +1 -0
  158. package/dist/types/src/types/types.d.ts +5 -0
  159. package/dist/types/src/types/types.d.ts.map +1 -0
  160. package/dist/types/tsconfig.tsbuildinfo +1 -0
  161. package/package.json +113 -0
  162. package/src/YouTubePlugin.tsx +66 -0
  163. package/src/blueprints/index.ts +7 -0
  164. package/src/blueprints/youtube.ts +52 -0
  165. package/src/capabilities/app-graph-builder/app-graph-builder.ts +148 -0
  166. package/src/capabilities/app-graph-builder/index.ts +7 -0
  167. package/src/capabilities/blueprint-definition/blueprint-definition.ts +17 -0
  168. package/src/capabilities/blueprint-definition/index.ts +7 -0
  169. package/src/capabilities/index.ts +7 -0
  170. package/src/capabilities/react-surface/index.ts +7 -0
  171. package/src/capabilities/react-surface/react-surface.tsx +54 -0
  172. package/src/containers/ChannelArticle/ChannelArticle.tsx +115 -0
  173. package/src/containers/ChannelArticle/index.ts +7 -0
  174. package/src/containers/ChannelSettings/ChannelSettings.tsx +34 -0
  175. package/src/containers/ChannelSettings/index.ts +7 -0
  176. package/src/containers/VideoArticle/VideoArticle.tsx +109 -0
  177. package/src/containers/VideoArticle/index.ts +7 -0
  178. package/src/containers/VideoCard/VideoCard.tsx +86 -0
  179. package/src/containers/VideoCard/index.ts +7 -0
  180. package/src/containers/index.ts +24 -0
  181. package/src/index.ts +8 -0
  182. package/src/meta.ts +19 -0
  183. package/src/operations/apis/index.ts +5 -0
  184. package/src/operations/apis/youtube/api.ts +149 -0
  185. package/src/operations/apis/youtube/index.ts +6 -0
  186. package/src/operations/apis/youtube/types.ts +254 -0
  187. package/src/operations/clear-synced-videos.ts +50 -0
  188. package/src/operations/definitions.ts +62 -0
  189. package/src/operations/index.ts +13 -0
  190. package/src/operations/services/google-credentials.ts +81 -0
  191. package/src/operations/services/index.ts +5 -0
  192. package/src/operations/sync.test.ts +114 -0
  193. package/src/operations/sync.ts +309 -0
  194. package/src/operations/transcript.ts +47 -0
  195. package/src/translations.ts +59 -0
  196. package/src/types/Channel.ts +80 -0
  197. package/src/types/Video.ts +67 -0
  198. package/src/types/index.ts +8 -0
  199. package/src/types/types.ts +10 -0
package/package.json ADDED
@@ -0,0 +1,113 @@
1
+ {
2
+ "name": "@dxos/plugin-youtube",
3
+ "version": "0.8.3",
4
+ "description": "DXOS YouTube feed plugin",
5
+ "homepage": "https://dxos.org",
6
+ "bugs": "https://github.com/dxos/dxos/issues",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/dxos/dxos"
10
+ },
11
+ "license": "MIT",
12
+ "author": "DXOS.org",
13
+ "sideEffects": true,
14
+ "type": "module",
15
+ "exports": {
16
+ ".": {
17
+ "source": "./src/index.ts",
18
+ "types": "./dist/types/src/index.d.ts",
19
+ "browser": "./dist/lib/browser/index.mjs",
20
+ "node": "./dist/lib/node-esm/index.mjs"
21
+ },
22
+ "./blueprints": {
23
+ "source": "./src/blueprints/index.ts",
24
+ "types": "./dist/types/src/blueprints/index.d.ts",
25
+ "browser": "./dist/lib/browser/blueprints/index.mjs",
26
+ "node": "./dist/lib/node-esm/blueprints/index.mjs"
27
+ },
28
+ "./types": {
29
+ "source": "./src/types/index.ts",
30
+ "types": "./dist/types/src/types/index.d.ts",
31
+ "browser": "./dist/lib/browser/types/index.mjs",
32
+ "node": "./dist/lib/node-esm/types/index.mjs"
33
+ }
34
+ },
35
+ "types": "dist/types/src/index.d.ts",
36
+ "typesVersions": {
37
+ "*": {
38
+ "blueprints": [
39
+ "dist/types/src/blueprints/index.d.ts"
40
+ ],
41
+ "types": [
42
+ "dist/types/src/types/index.d.ts"
43
+ ]
44
+ }
45
+ },
46
+ "files": [
47
+ "dist",
48
+ "src"
49
+ ],
50
+ "dependencies": {
51
+ "@effect-atom/atom-react": "^0.5.0",
52
+ "@effect/platform": "0.94.4",
53
+ "@radix-ui/react-context": "1.1.1",
54
+ "effect": "3.20.0",
55
+ "youtube-caption-extractor": "^1.9.1",
56
+ "@dxos/blueprints": "0.8.3",
57
+ "@dxos/app-framework": "0.8.3",
58
+ "@dxos/app-toolkit": "0.8.3",
59
+ "@dxos/async": "0.8.3",
60
+ "@dxos/client": "0.8.3",
61
+ "@dxos/debug": "0.8.3",
62
+ "@dxos/echo-atom": "0.8.3",
63
+ "@dxos/echo": "0.8.3",
64
+ "@dxos/effect": "0.8.3",
65
+ "@dxos/echo-db": "0.8.3",
66
+ "@dxos/invariant": "0.8.3",
67
+ "@dxos/errors": "0.8.3",
68
+ "@dxos/plugin-attention": "0.8.3",
69
+ "@dxos/log": "0.8.3",
70
+ "@dxos/operation": "0.8.3",
71
+ "@dxos/plugin-automation": "0.8.3",
72
+ "@dxos/plugin-client": "0.8.3",
73
+ "@dxos/functions": "0.8.3",
74
+ "@dxos/plugin-graph": "0.8.3",
75
+ "@dxos/plugin-space": "0.8.3",
76
+ "@dxos/react-ui-attention": "0.8.3",
77
+ "@dxos/schema": "0.8.3",
78
+ "@dxos/react-ui": "0.8.3",
79
+ "@dxos/types": "0.8.3",
80
+ "@dxos/react-client": "0.8.3",
81
+ "@dxos/keys": "0.8.3",
82
+ "@dxos/plugin-deck": "0.8.3",
83
+ "@dxos/util": "0.8.3",
84
+ "@dxos/react-ui-stack": "0.8.3"
85
+ },
86
+ "devDependencies": {
87
+ "@types/react": "~19.2.7",
88
+ "@types/react-dom": "~19.2.3",
89
+ "react": "~19.2.3",
90
+ "react-dom": "~19.2.3",
91
+ "vite": "^7.1.11",
92
+ "@dxos/plugin-testing": "0.8.3",
93
+ "@dxos/plugin-theme": "0.8.3",
94
+ "@dxos/protocols": "0.8.3",
95
+ "@dxos/config": "0.8.3",
96
+ "@dxos/react-ui": "0.8.3",
97
+ "@dxos/random": "0.8.3",
98
+ "@dxos/storybook-utils": "0.8.3",
99
+ "@dxos/ui-theme": "0.8.3"
100
+ },
101
+ "peerDependencies": {
102
+ "@effect-atom/atom-react": "^0.5.0",
103
+ "@effect/platform": "0.94.4",
104
+ "effect": "3.20.0",
105
+ "react": "~19.2.3",
106
+ "react-dom": "~19.2.3",
107
+ "@dxos/react-ui": "0.8.3",
108
+ "@dxos/ui-theme": "0.8.3"
109
+ },
110
+ "publishConfig": {
111
+ "access": "public"
112
+ }
113
+ }
@@ -0,0 +1,66 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+ import * as Option from 'effect/Option';
7
+
8
+ import { ActivationEvent, Plugin } from '@dxos/app-framework';
9
+ import { AppActivationEvents, AppPlugin } from '@dxos/app-toolkit';
10
+ import { Annotation } from '@dxos/echo';
11
+ import { Operation } from '@dxos/operation';
12
+ import { AttentionEvents } from '@dxos/plugin-attention';
13
+ import { type CreateObject } from '@dxos/plugin-space/types';
14
+ import { SpaceOperation } from '@dxos/plugin-space/operations';
15
+
16
+ import { YouTubeBlueprint } from './blueprints';
17
+ import { AppGraphBuilder, BlueprintDefinition, ReactSurface } from './capabilities';
18
+ import { meta } from './meta';
19
+ import { translations } from './translations';
20
+ import { Channel, Video } from './types';
21
+
22
+ export const YouTubePlugin = Plugin.define(meta).pipe(
23
+ AppPlugin.addAppGraphModule({
24
+ activatesOn: ActivationEvent.allOf(AppActivationEvents.SetupAppGraph, AttentionEvents.AttentionReady),
25
+ activate: AppGraphBuilder,
26
+ }),
27
+ AppPlugin.addBlueprintDefinitionModule({ activate: BlueprintDefinition }),
28
+ AppPlugin.addMetadataModule({
29
+ metadata: [
30
+ {
31
+ id: Channel.YouTubeChannel.typename,
32
+ metadata: {
33
+ label: (object: Channel.YouTubeChannel) => object.name ?? object.channelUrl ?? object.channelId ?? 'YouTube',
34
+ icon: Annotation.IconAnnotation.get(Channel.YouTubeChannel).pipe(Option.getOrThrow).icon,
35
+ iconHue: Annotation.IconAnnotation.get(Channel.YouTubeChannel).pipe(Option.getOrThrow).hue ?? 'white',
36
+ blueprints: [YouTubeBlueprint.key],
37
+ inputSchema: Channel.CreateYouTubeChannelSchema,
38
+ createObject: ((props, options) =>
39
+ Effect.gen(function* () {
40
+ const object = Channel.make(props);
41
+ return yield* Operation.invoke(SpaceOperation.AddObject, {
42
+ object,
43
+ target: options.target,
44
+ hidden: true,
45
+ targetNodeId: options.targetNodeId,
46
+ });
47
+ })) satisfies CreateObject,
48
+ },
49
+ },
50
+ {
51
+ id: Video.YouTubeVideo.typename,
52
+ metadata: {
53
+ label: (object: Video.YouTubeVideo) => object.title,
54
+ icon: Annotation.IconAnnotation.get(Video.YouTubeVideo).pipe(Option.getOrThrow).icon,
55
+ iconHue: Annotation.IconAnnotation.get(Video.YouTubeVideo).pipe(Option.getOrThrow).hue ?? 'white',
56
+ },
57
+ },
58
+ ],
59
+ }),
60
+ AppPlugin.addSchemaModule({
61
+ schema: [Channel.YouTubeChannel, Video.YouTubeVideo],
62
+ }),
63
+ AppPlugin.addSurfaceModule({ activate: ReactSurface }),
64
+ AppPlugin.addTranslationsModule({ translations }),
65
+ Plugin.make,
66
+ );
@@ -0,0 +1,7 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import YouTubeBlueprint from './youtube';
6
+
7
+ export { YouTubeBlueprint };
@@ -0,0 +1,52 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { type AppCapabilities } from '@dxos/app-toolkit';
6
+ import { Blueprint, Template } from '@dxos/blueprints';
7
+ import { trim } from '@dxos/util';
8
+
9
+ import { ClearSyncedVideos, Sync } from '../operations';
10
+
11
+ const BLUEPRINT_KEY = 'dxos.org/blueprint/youtube';
12
+
13
+ const make = () =>
14
+ Blueprint.make({
15
+ key: BLUEPRINT_KEY,
16
+ name: 'YouTube',
17
+ tools: Blueprint.toolDefinitions({ operations: [Sync, ClearSyncedVideos], tools: [] }),
18
+ instructions: Template.make({
19
+ source: trim`
20
+ You manage YouTube channel subscriptions and video content.
21
+
22
+ # Summary formatting:
23
+ - Format summaries as markdown documents without extra comments.
24
+ - Use markdown formatting for headings and bullet points.
25
+ - Format video summaries as lists with key points.
26
+
27
+ # References
28
+ - Use references to objects in the form of:
29
+ @dxn:queue:data:B6INSIBY3CBEF4M5VZRYBCMAHQMPYK5AJ:01K24XMVHSZHS97SG1VTVQDM5Z:01K24XPK464FSCKVQJAB2H662M
30
+ - References are rendered as rich content in the response to the user.
31
+
32
+ # Video Analysis
33
+ When analyzing videos:
34
+ - Summarize the key topics and themes.
35
+ - Extract important quotes or statements.
36
+ - Identify action items or recommendations.
37
+ - Note any timestamps for important sections.
38
+
39
+ # Transcript Usage
40
+ - Videos may include transcripts that can be used for deeper analysis.
41
+ - When summarizing content, reference the transcript for accuracy.
42
+ - Transcripts include timestamps that can help locate specific content.
43
+ `,
44
+ }),
45
+ });
46
+
47
+ const blueprint: AppCapabilities.BlueprintDefinition = {
48
+ key: BLUEPRINT_KEY,
49
+ make,
50
+ };
51
+
52
+ export default blueprint;
@@ -0,0 +1,148 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { Atom } from '@effect-atom/atom-react';
6
+ import * as Effect from 'effect/Effect';
7
+ import * as Option from 'effect/Option';
8
+
9
+ import { Capability } from '@dxos/app-framework';
10
+ import { AppCapabilities, companionSegment, LayoutOperation } from '@dxos/app-toolkit';
11
+ import { type Feed, Filter, Obj, Query, Ref } from '@dxos/echo';
12
+ import { AtomQuery, AtomRef } from '@dxos/echo-atom';
13
+ import { invariant } from '@dxos/invariant';
14
+ import { log } from '@dxos/log';
15
+ import { Operation } from '@dxos/operation';
16
+ import { AttentionCapabilities } from '@dxos/plugin-attention';
17
+ import { AutomationCapabilities, invokeFunctionWithTracing } from '@dxos/plugin-automation';
18
+ import { PLANK_COMPANION_TYPE } from '@dxos/plugin-deck/types';
19
+ import { GraphBuilder, NodeMatcher } from '@dxos/plugin-graph';
20
+
21
+ import { ClearSyncedVideos, Sync } from '../../operations';
22
+ import { meta } from '../../meta';
23
+ import { Channel, Video } from '../../types';
24
+
25
+ export default Capability.makeModule(
26
+ Effect.fnUntraced(function* () {
27
+ const selectionManager = yield* Capability.get(AttentionCapabilities.Selection);
28
+ const selectedId = Atom.family((nodeId: string) =>
29
+ Atom.make((get) => {
30
+ const state = get(selectionManager.state);
31
+ const selection = state.selections[nodeId];
32
+ return selection?.mode === 'single' ? selection.id : undefined;
33
+ }),
34
+ );
35
+
36
+ const whenYouTubeChannel = NodeMatcher.whenEchoType(Channel.YouTubeChannel);
37
+
38
+ const extensions = yield* Effect.all([
39
+ GraphBuilder.createExtension({
40
+ id: `${meta.id}.channel-video`,
41
+ match: (node) =>
42
+ Channel.instanceOf(node.data) ? Option.some({ channel: node.data, nodeId: node.id }) : Option.none(),
43
+ connector: (matched, get) => {
44
+ const channel = matched.channel;
45
+ const db = Obj.getDatabase(channel);
46
+ const feed = channel.feed ? (get(AtomRef.make(channel.feed)) as Feed.Feed | undefined) : undefined;
47
+ if (!db || !feed) {
48
+ return Effect.succeed([]);
49
+ }
50
+
51
+ const videoId = get(selectedId(matched.nodeId));
52
+ const video = get(
53
+ AtomQuery.make<Video.YouTubeVideo>(
54
+ db,
55
+ Query.select(videoId ? Filter.id(videoId) : Filter.nothing()).from(feed),
56
+ ),
57
+ )[0];
58
+ return Effect.succeed([
59
+ {
60
+ id: companionSegment('video'),
61
+ type: PLANK_COMPANION_TYPE,
62
+ data: video ?? 'video',
63
+ properties: {
64
+ label: ['video label', { ns: meta.id }],
65
+ icon: 'ph--play--regular',
66
+ disposition: 'hidden',
67
+ },
68
+ },
69
+ ]);
70
+ },
71
+ }),
72
+
73
+ GraphBuilder.createExtension({
74
+ id: `${meta.id}.sync-channel`,
75
+ match: whenYouTubeChannel,
76
+ actions: (channel) =>
77
+ Effect.succeed([
78
+ {
79
+ id: 'sync',
80
+ data: Effect.fnUntraced(function* () {
81
+ const computeRuntime = yield* Capability.get(AutomationCapabilities.ComputeRuntime);
82
+ const db = Obj.getDatabase(channel);
83
+ invariant(db);
84
+ const runtime = computeRuntime.getRuntime(db.spaceId);
85
+ yield* Effect.tryPromise(() =>
86
+ runtime.runPromise(
87
+ invokeFunctionWithTracing(Sync, {
88
+ channel: Ref.make(channel),
89
+ }),
90
+ ),
91
+ ).pipe(
92
+ Effect.catchAll((error) => {
93
+ log.catch(error);
94
+ return Operation.invoke(LayoutOperation.AddToast, {
95
+ id: `${meta.id}.sync-channel-error`,
96
+ icon: 'ph--warning--regular',
97
+ duration: 5_000,
98
+ title: ['sync channel error title', { ns: meta.id }],
99
+ closeLabel: ['close label', { ns: meta.id }],
100
+ });
101
+ }),
102
+ );
103
+ }),
104
+ properties: {
105
+ label: ['sync channel label', { ns: meta.id }],
106
+ icon: 'ph--arrows-clockwise--regular',
107
+ disposition: 'list-item',
108
+ },
109
+ },
110
+ {
111
+ id: 'clear-synced-videos',
112
+ data: Effect.fnUntraced(function* () {
113
+ const computeRuntime = yield* Capability.get(AutomationCapabilities.ComputeRuntime);
114
+ const db = Obj.getDatabase(channel);
115
+ invariant(db);
116
+ const runtime = computeRuntime.getRuntime(db.spaceId);
117
+ yield* Effect.tryPromise(() =>
118
+ runtime.runPromise(
119
+ invokeFunctionWithTracing(ClearSyncedVideos, {
120
+ channel: Ref.make(channel),
121
+ }),
122
+ ),
123
+ ).pipe(
124
+ Effect.catchAll((error) => {
125
+ log.catch(error);
126
+ return Operation.invoke(LayoutOperation.AddToast, {
127
+ id: `${meta.id}.clear-synced-videos-error`,
128
+ icon: 'ph--warning--regular',
129
+ duration: 5_000,
130
+ title: ['clear synced videos error title', { ns: meta.id }],
131
+ closeLabel: ['close label', { ns: meta.id }],
132
+ });
133
+ }),
134
+ );
135
+ }),
136
+ properties: {
137
+ label: ['clear synced videos label', { ns: meta.id }],
138
+ icon: 'ph--trash--regular',
139
+ disposition: 'list-item',
140
+ },
141
+ },
142
+ ]),
143
+ }),
144
+ ]);
145
+
146
+ return Capability.contributes(AppCapabilities.AppGraphBuilder, extensions);
147
+ }),
148
+ );
@@ -0,0 +1,7 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { Capability } from '@dxos/app-framework';
6
+
7
+ export const AppGraphBuilder = Capability.lazy('AppGraphBuilder', () => import('./app-graph-builder'));
@@ -0,0 +1,17 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+
7
+ import { Capability } from '@dxos/app-framework';
8
+ import { AppCapabilities } from '@dxos/app-toolkit';
9
+
10
+ import { YouTubeBlueprint } from '../../blueprints';
11
+
12
+ const blueprintDefinition = Capability.makeModule<
13
+ [],
14
+ Capability.Capability<typeof AppCapabilities.BlueprintDefinition>[]
15
+ >(() => Effect.succeed([Capability.contributes(AppCapabilities.BlueprintDefinition, YouTubeBlueprint)]));
16
+
17
+ export default blueprintDefinition;
@@ -0,0 +1,7 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { Capability } from '@dxos/app-framework';
6
+
7
+ export const BlueprintDefinition = Capability.lazy('BlueprintDefinition', () => import('./blueprint-definition'));
@@ -0,0 +1,7 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ export * from './app-graph-builder';
6
+ export * from './blueprint-definition';
7
+ export * from './react-surface';
@@ -0,0 +1,7 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { Capability } from '@dxos/app-framework';
6
+
7
+ export const ReactSurface = Capability.lazy('ReactSurface', () => import('./react-surface'));
@@ -0,0 +1,54 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+ import React from 'react';
7
+
8
+ import { Capabilities, Capability } from '@dxos/app-framework';
9
+ import { Surface } from '@dxos/app-framework/ui';
10
+
11
+ import { ChannelArticle, ChannelSettings, VideoArticle, VideoCard } from '../../containers';
12
+ import { meta } from '../../meta';
13
+ import { Channel, Video } from '../../types';
14
+
15
+ export default Capability.makeModule(() =>
16
+ Effect.succeed(
17
+ Capability.contributes(Capabilities.ReactSurface, [
18
+ Surface.create({
19
+ id: `${meta.id}.channel`,
20
+ role: ['article'],
21
+ filter: (data): data is { attendableId?: string; subject: Channel.YouTubeChannel } =>
22
+ Channel.instanceOf(data.subject),
23
+ component: ({ data }) => {
24
+ return <ChannelArticle subject={data.subject} attendableId={data.attendableId} />;
25
+ },
26
+ }),
27
+ Surface.create({
28
+ id: `${meta.id}.video`,
29
+ role: ['article', 'section'],
30
+ filter: (
31
+ data,
32
+ ): data is { attendableId: string; subject: Video.YouTubeVideo; companionTo: Channel.YouTubeChannel } =>
33
+ typeof data.attendableId === 'string' &&
34
+ Video.instanceOf(data.subject) &&
35
+ Channel.instanceOf(data.companionTo),
36
+ component: ({ data: { attendableId, companionTo, subject }, role }) => {
37
+ return <VideoArticle role={role} subject={subject} channel={companionTo} attendableId={attendableId} />;
38
+ },
39
+ }),
40
+ Surface.create({
41
+ id: `${meta.id}.video-card`,
42
+ role: 'card--content',
43
+ filter: (data): data is { subject: Video.YouTubeVideo } => Video.instanceOf(data?.subject),
44
+ component: ({ data: { subject }, role }) => <VideoCard subject={subject} role={role} />,
45
+ }),
46
+ Surface.create({
47
+ id: `${meta.id}.channel.companion.settings`,
48
+ role: 'object-settings',
49
+ filter: (data): data is { subject: Channel.YouTubeChannel } => Channel.instanceOf(data.subject),
50
+ component: ({ data }) => <ChannelSettings subject={data.subject} />,
51
+ }),
52
+ ]),
53
+ ),
54
+ );
@@ -0,0 +1,115 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import React, { useMemo } from 'react';
6
+
7
+ import { type Feed, Obj, Query } from '@dxos/echo';
8
+ import { Filter, useObject, useQuery } from '@dxos/react-client/echo';
9
+ import { Icon, Panel } from '@dxos/react-ui';
10
+
11
+ import * as Channel from '../../types/Channel';
12
+ import * as Video from '../../types/Video';
13
+
14
+ export type ChannelArticleProps = {
15
+ subject: Channel.YouTubeChannel;
16
+ attendableId?: string;
17
+ };
18
+
19
+ export const ChannelArticle = ({ subject: channel }: ChannelArticleProps) => {
20
+ useObject(channel);
21
+ const feed = channel.feed?.target as Feed.Feed | undefined;
22
+ const db = Obj.getDatabase(channel);
23
+ const videos = useQuery(
24
+ db,
25
+ feed ? Query.select(Filter.type(Video.YouTubeVideo)).from(feed) : Query.select(Filter.nothing()),
26
+ ) as Video.YouTubeVideo[];
27
+
28
+ const sortedVideos = useMemo(
29
+ () =>
30
+ [...videos].sort(
31
+ (videoA, videoB) => new Date(videoB.publishedAt).getTime() - new Date(videoA.publishedAt).getTime(),
32
+ ),
33
+ [videos],
34
+ );
35
+
36
+ return (
37
+ <Panel.Root>
38
+ <Panel.Content className='overflow-auto'>
39
+ <div className='flex flex-col gap-4 p-4'>
40
+ <div className='flex items-center justify-between'>
41
+ <div className='flex items-center gap-2'>
42
+ <Icon icon='ph--youtube-logo--regular' size={6} />
43
+ <h2 className='text-lg font-semibold'>{channel.name ?? 'YouTube Channel'}</h2>
44
+ </div>
45
+ {channel.lastSyncedAt && (
46
+ <span className='text-xs text-description'>
47
+ Last synced: {new Date(channel.lastSyncedAt).toLocaleString()}
48
+ </span>
49
+ )}
50
+ </div>
51
+
52
+ {channel.channelUrl && (
53
+ <div className='text-sm text-description'>
54
+ <a
55
+ href={
56
+ channel.channelUrl.startsWith('http')
57
+ ? channel.channelUrl
58
+ : `https://www.youtube.com/@${channel.channelUrl}`
59
+ }
60
+ target='_blank'
61
+ rel='noopener noreferrer'
62
+ className='hover:underline'
63
+ >
64
+ {channel.channelUrl}
65
+ </a>
66
+ </div>
67
+ )}
68
+
69
+ <div className='flex flex-col gap-2'>
70
+ <h3 className='text-md font-medium'>Videos ({sortedVideos.length})</h3>
71
+ <div className='grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4'>
72
+ {sortedVideos.map((video) => (
73
+ <div key={video.videoId} className='flex flex-col gap-2 p-2 rounded hover:bg-surface-hover'>
74
+ {video.thumbnailUrl ? (
75
+ <a
76
+ href={video.url}
77
+ target='_blank'
78
+ rel='noopener noreferrer'
79
+ className='relative aspect-video group'
80
+ >
81
+ <img src={video.thumbnailUrl} alt={video.title} className='h-full w-full object-cover rounded' />
82
+ <div className='absolute inset-0 flex items-center justify-center bg-black/0 group-hover:bg-black/30 rounded transition-colors'>
83
+ <div className='opacity-0 group-hover:opacity-100 bg-red-600 text-white rounded-full p-2 transition-opacity'>
84
+ <Icon icon='ph--play--fill' size={4} />
85
+ </div>
86
+ </div>
87
+ </a>
88
+ ) : (
89
+ <div className='aspect-video bg-surface-hover rounded flex items-center justify-center'>
90
+ <Icon icon='ph--video--regular' size={8} />
91
+ </div>
92
+ )}
93
+ <div className='flex flex-col gap-1'>
94
+ <span className='font-medium line-clamp-2' title={video.title}>
95
+ {video.title}
96
+ </span>
97
+ <span className='text-xs text-description'>
98
+ {new Date(video.publishedAt).toLocaleDateString()}
99
+ {video.transcript && ' • Transcript available'}
100
+ </span>
101
+ </div>
102
+ </div>
103
+ ))}
104
+ {sortedVideos.length === 0 && (
105
+ <div className='col-span-full text-sm text-description p-4 text-center'>
106
+ No videos synced yet. Sync the channel to fetch videos.
107
+ </div>
108
+ )}
109
+ </div>
110
+ </div>
111
+ </div>
112
+ </Panel.Content>
113
+ </Panel.Root>
114
+ );
115
+ };
@@ -0,0 +1,7 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { ChannelArticle } from './ChannelArticle';
6
+
7
+ export default ChannelArticle;
@@ -0,0 +1,34 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import React from 'react';
6
+
7
+ import * as Channel from '../../types/Channel';
8
+
9
+ export type ChannelSettingsProps = {
10
+ subject: Channel.YouTubeChannel;
11
+ };
12
+
13
+ export const ChannelSettings = ({ subject: channel }: ChannelSettingsProps) => {
14
+ return (
15
+ <div className='flex flex-col gap-4 p-4'>
16
+ <h3 className='text-md font-medium'>Channel Settings</h3>
17
+ <div className='flex flex-col gap-2 text-sm'>
18
+ <div>
19
+ <span className='text-description'>Name:</span> <span>{channel.name ?? 'Not set'}</span>
20
+ </div>
21
+ <div>
22
+ <span className='text-description'>Channel URL:</span> <span>{channel.channelUrl ?? 'Not set'}</span>
23
+ </div>
24
+ <div>
25
+ <span className='text-description'>Channel ID:</span> <span>{channel.channelId ?? 'Not set'}</span>
26
+ </div>
27
+ <div>
28
+ <span className='text-description'>Last Synced:</span>{' '}
29
+ <span>{channel.lastSyncedAt ? new Date(channel.lastSyncedAt).toLocaleString() : 'Never'}</span>
30
+ </div>
31
+ </div>
32
+ </div>
33
+ );
34
+ };
@@ -0,0 +1,7 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { ChannelSettings } from './ChannelSettings';
6
+
7
+ export default ChannelSettings;