@surph_ai/sdk 0.0.1

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 (70) hide show
  1. package/auth/index.js +147 -0
  2. package/base/vinely-base/.nvmrc +1 -0
  3. package/base/vinely-base/README.md +30 -0
  4. package/base/vinely-base/app.js +5 -0
  5. package/base/vinely-base/declarations.d.ts +1 -0
  6. package/base/vinely-base/package.json +103 -0
  7. package/base/vinely-base/public/css/globals-2.css +3699 -0
  8. package/base/vinely-base/public/css/globals.css +3695 -0
  9. package/base/vinely-base/public/js/app.js +1 -0
  10. package/base/vinely-base/public/js/jquery.js +2 -0
  11. package/base/vinely-base/scratch.js +21 -0
  12. package/base/vinely-base/src/client/components/app-sidebar.tsx +59 -0
  13. package/base/vinely-base/src/client/components/assistant-ui/markdown-text.tsx +153 -0
  14. package/base/vinely-base/src/client/components/assistant-ui/thread-list.tsx +67 -0
  15. package/base/vinely-base/src/client/components/assistant-ui/thread.tsx +666 -0
  16. package/base/vinely-base/src/client/components/assistant-ui/tool-fallback.tsx +44 -0
  17. package/base/vinely-base/src/client/components/assistant-ui/tooltip-icon-button.tsx +44 -0
  18. package/base/vinely-base/src/client/components/ui/breadcrumb.tsx +109 -0
  19. package/base/vinely-base/src/client/components/ui/button.tsx +59 -0
  20. package/base/vinely-base/src/client/components/ui/input.tsx +21 -0
  21. package/base/vinely-base/src/client/components/ui/separator.tsx +28 -0
  22. package/base/vinely-base/src/client/components/ui/sheet.tsx +139 -0
  23. package/base/vinely-base/src/client/components/ui/sidebar.tsx +726 -0
  24. package/base/vinely-base/src/client/components/ui/skeleton.tsx +14 -0
  25. package/base/vinely-base/src/client/components/ui/tooltip.tsx +61 -0
  26. package/base/vinely-base/src/client/components/vines-sidebar.tsx +182 -0
  27. package/base/vinely-base/src/client/contexts/SessionContext.tsx +28 -0
  28. package/base/vinely-base/src/client/contexts/SessionReducer.tsx +32 -0
  29. package/base/vinely-base/src/client/hooks/use-mobile.ts +19 -0
  30. package/base/vinely-base/src/client/index.tsx +154 -0
  31. package/base/vinely-base/src/client/lib/API.ts +155 -0
  32. package/base/vinely-base/src/client/lib/RPC.ts +49 -0
  33. package/base/vinely-base/src/client/lib/utils.ts +96 -0
  34. package/base/vinely-base/src/server/index.ts +63 -0
  35. package/base/vinely-base/src/server/routes/api.ts +38 -0
  36. package/base/vinely-base/src/server/routes/chat.ts +133 -0
  37. package/base/vinely-base/src/server/routes/main.ts +36 -0
  38. package/base/vinely-base/src/server/routes/mcp.ts +52 -0
  39. package/base/vinely-base/src/server/routes/rpc.ts +64 -0
  40. package/base/vinely-base/src/server/routes/thread.ts +44 -0
  41. package/base/vinely-base/src/server/utils/APIMgr.ts +156 -0
  42. package/base/vinely-base/src/server/utils/Auth.ts +235 -0
  43. package/base/vinely-base/src/server/utils/fetchManifest.ts +15 -0
  44. package/base/vinely-base/src/server/utils/mcp-auth.ts +251 -0
  45. package/base/vinely-base/tsconfig.json +16 -0
  46. package/base/vinely-base/views/landing.html +187 -0
  47. package/base/vinely-base/views/partials/imports.dev.html +3 -0
  48. package/base/vinely-base/webpack.config.js +112 -0
  49. package/deploy/Dockerfile +9 -0
  50. package/deploy/index.js +244 -0
  51. package/index.js +266 -0
  52. package/package.json +28 -0
  53. package/scaffold/app/gulpfile.js +12 -0
  54. package/scaffold/app/package.json +24 -0
  55. package/scaffold/app/src/index.js +14 -0
  56. package/scaffold/index.js +171 -0
  57. package/scaffold/project/README.md +28 -0
  58. package/scaffold/project/app.js +20 -0
  59. package/scaffold/project/apps/README.md +1 -0
  60. package/scaffold/project/env.txt +4 -0
  61. package/scaffold/project/package-lock.json +2459 -0
  62. package/scaffold/project/package.json +21 -0
  63. package/scaffold/project/public/css/bootstrap.css +5926 -0
  64. package/scaffold/project/public/js/app.js +1 -0
  65. package/scaffold/project/public/js/jquery.js +2 -0
  66. package/scaffold/project/routes/index.js +8 -0
  67. package/scaffold/project/templates/index.mustache +20 -0
  68. package/utils/Realm.js +60 -0
  69. package/utils/http.js +22 -0
  70. package/utils/index.js +276 -0
@@ -0,0 +1,235 @@
1
+ import { Response, NextFunction, Express } from 'express'
2
+ import crypto from 'crypto'
3
+ import passport from "passport"
4
+ import { Strategy as GoogleStrategy } from "passport-google-oauth20"
5
+ import APIMgr from './APIMgr.js'
6
+
7
+ const COOKIE_NAME: string = process.env.COOKIE_NAME || ''
8
+ const USER_CACHE: any = {}
9
+
10
+ const GOOGLE_CLIENT_ID: string = process.env.GOOGLE_CLIENT_ID as string
11
+ const GOOGLE_CLIENT_SECRET: string = process.env.GOOGLE_CLIENT_SECRET as string
12
+ const GOOGLE_CALLBACK_URL: string = process.env.GOOGLE_CALLBACK_URL as string
13
+
14
+ const generateSalt = () => {
15
+ return new Promise((resolve, reject) => {
16
+ const byteSize = 16
17
+ crypto.randomBytes(byteSize, (err, salt) => {
18
+ if (err) {
19
+ reject(err)
20
+ return
21
+ }
22
+
23
+ resolve(salt.toString('base64'))
24
+ })
25
+ })
26
+ }
27
+
28
+ const hashPw = (password: string, salt: string) => {
29
+ return new Promise((resolve, reject) => {
30
+ const defaultIterations = 10000
31
+ const defaultKeyLength = 64
32
+ // const saltBase64 = new Buffer(salt, 'base64')
33
+ const saltBase64 = Buffer.from(salt, 'base64')
34
+ const digest = 'sha512'
35
+
36
+ crypto.pbkdf2(
37
+ password,
38
+ saltBase64,
39
+ defaultIterations,
40
+ defaultKeyLength,
41
+ digest,
42
+ (err, key) => {
43
+ if (err) {
44
+ reject(err)
45
+ return
46
+ }
47
+
48
+ resolve(key.toString('base64'))
49
+ }
50
+ )
51
+ })
52
+ }
53
+
54
+ const authenticate = async (password: string, profile: any) => {
55
+ const hashed = await hashPw(password, profile.auth.salt)
56
+ return (hashed === profile.auth.pw)
57
+ }
58
+
59
+ const setToken = (res: Response, cookieValue: string, tknName?: string) => {
60
+ const token = tknName || process.env.COOKIE_NAME as string
61
+ // console.log('\nSET TOKEN: ' + token + ': ' + cookieValue + '\n')
62
+ const ONE_DAY = 24 * 60 * 60 * 1000
63
+ const domain = (process.env.TURBO_ENV === 'local') ? 'localhost' : '.vinely.ai'
64
+
65
+ // const cookieStr = cookieValue || ''
66
+ res.cookie(token, cookieValue, {
67
+ maxAge: 14 * ONE_DAY,
68
+ httpOnly: true,
69
+ domain,
70
+ })
71
+ }
72
+
73
+ const clearToken = (res: Response, tknName?: string) => {
74
+ const token = tknName || process.env.COOKIE_NAME as string
75
+ const domain = (process.env.TURBO_ENV === 'local') ? 'localhost' : '.vinely.ai'
76
+ res.clearCookie(token, { domain })
77
+ }
78
+
79
+ const sessionMiddleware = async (req: any, res: Response, next: NextFunction) => {
80
+ if (!req.cookies) {
81
+ console.log('SESSION NOT FOUND: ')
82
+ return next()
83
+ }
84
+
85
+ const cookie = req.cookies[COOKIE_NAME]
86
+ if (!cookie) {
87
+ console.log('NO VINLEY TKN: ' + JSON.stringify(req.cookies))
88
+ return next()
89
+ }
90
+
91
+ if (cookie === 'anon') {
92
+ console.log('NOT LOGGED IN: ' + JSON.stringify(req.cookies))
93
+ return next()
94
+ }
95
+
96
+ try {
97
+ const session = JSON.parse(cookie)
98
+ // session.user = '68bb5f2b6d70d5c695588dd4' // hayden
99
+ // session.user = '68bf432cdcf8995ba8c48519' // tess
100
+
101
+ let user = USER_CACHE[session.user]
102
+ if (user) {
103
+ req._session = user
104
+ } else {
105
+ const { payload } = await APIMgr.get(`user/${session.user}`, null, null)
106
+ delete payload.auth
107
+ delete payload.email
108
+
109
+ USER_CACHE[session.user] = payload
110
+ req._session = payload
111
+ }
112
+ } catch (error) {
113
+ res.clearCookie(COOKIE_NAME) // invalidate token
114
+ } finally {
115
+ next()
116
+ }
117
+ }
118
+
119
+ const setupPassport = (app: Express) => {
120
+ passport.use(new GoogleStrategy({
121
+ clientID: GOOGLE_CLIENT_ID,
122
+ clientSecret: GOOGLE_CLIENT_SECRET,
123
+ // callbackURL: "http://localhost:8080/oauth/google/callback"
124
+ callbackURL: GOOGLE_CALLBACK_URL
125
+ }, (accessToken, refreshToken, profile, done) => {
126
+
127
+ // TODO: check if user exists in DB or create a new one
128
+ console.log('GOOGLE PROFILE: ' + JSON.stringify(profile))
129
+ return done(null, profile);
130
+ }));
131
+
132
+ app.use(passport.initialize())
133
+ // app.use(passport.session())
134
+
135
+ // Serialize/deserialize user
136
+ passport.serializeUser((user: any, done) => {
137
+ done(null, user)
138
+ })
139
+
140
+ passport.deserializeUser((user: any, done) => {
141
+ done(null, user)
142
+ })
143
+
144
+ app.get(
145
+ '/oauth/google',
146
+ passport.authenticate("google", { scope: ["profile", "email"] })
147
+ )
148
+
149
+ app.get(
150
+ '/oauth/google/callback',
151
+ passport.authenticate("google", { session: false, failureRedirect: "/" }),
152
+ async (req, res) => {
153
+ const userData = req.user as any
154
+ const user = userData._json
155
+
156
+ /*
157
+ {
158
+ "sub": "105065442288013194527",
159
+ "name": "Joe Rogan",
160
+ "given_name": "Joe",
161
+ "family_name": "Rogan",
162
+ "picture": "https://lh3.googleusercontent.com/a/ACg8ocIefrpbU2qhABOKkJW3Ro_1_47nOBnYGI2uAp4H5h4BXvIk2w=s96-c",
163
+ "email": "joe.rogan@gmail.com",
164
+ "email_verified": true
165
+ }
166
+ */
167
+
168
+ const { payload } = await APIMgr.get('user', { email: user.email }, null)
169
+
170
+ if (payload.length > 0) { // user already registerd
171
+ const currentUser = payload[0]
172
+
173
+ if (currentUser.auth.type !== 'google') {
174
+ res.status(403).json({
175
+ error: 'User not registered with google'
176
+ })
177
+
178
+ return
179
+ }
180
+
181
+ setToken(res, JSON.stringify({ user: currentUser._id }))
182
+ res.redirect('/')
183
+ return
184
+ }
185
+
186
+ const { token } = await registerUser(user, 'google')
187
+ setToken(res, token)
188
+ res.redirect('/')
189
+ }
190
+ )
191
+ }
192
+
193
+ const registerUser = async (user: any, type: string) => {
194
+ const { email } = user
195
+
196
+ const formattedEmail = email.toLowerCase().trim()
197
+ const params: any = {
198
+ email: formattedEmail,
199
+ username: formattedEmail.split('@')[0]
200
+ }
201
+
202
+ const auth: any = { type } // email, google, linkedin, etc
203
+
204
+ if (type === 'email') {
205
+ const password = user.password
206
+ auth.salt = await generateSalt()
207
+ auth.pw = await hashPw(password, auth.salt)
208
+ }
209
+
210
+ if (type === 'google') {
211
+ auth.googleId = user.sub
212
+ }
213
+
214
+ params.auth = auth
215
+ const { payload } = await APIMgr.post('user', params, null)
216
+ delete payload.auth
217
+
218
+ const profile = payload
219
+
220
+ return {
221
+ user: profile,
222
+ token: JSON.stringify({ user: profile._id })
223
+ }
224
+ }
225
+
226
+ export {
227
+ generateSalt,
228
+ hashPw,
229
+ authenticate,
230
+ setToken,
231
+ clearToken,
232
+ sessionMiddleware,
233
+ setupPassport,
234
+ registerUser
235
+ }
@@ -0,0 +1,15 @@
1
+ import axios from 'axios'
2
+
3
+ export default async () => {
4
+ const url = `https://vinely.s3.us-east-1.amazonaws.com/sites/${process.env.VLY_APP}/manifest.txt`
5
+
6
+ const { data } = await axios({
7
+ url,
8
+ method: 'get',
9
+ headers: {
10
+ Accept: 'application/json'
11
+ }
12
+ })
13
+
14
+ return data
15
+ }
@@ -0,0 +1,251 @@
1
+ import { OAuthClientProvider, UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js";
2
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
3
+ import { OAuthClientInformation, OAuthClientInformationFull, OAuthTokens, OAuthClientMetadata } from "@modelcontextprotocol/sdk/shared/auth.js";
4
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
5
+
6
+ export class WebOAuthClientProvider implements OAuthClientProvider {
7
+ private _redirectUrl: string | URL;
8
+ //private _clientMetadata: OAuthClientMetadata;
9
+ private _clientInformation?: OAuthClientInformation | undefined;
10
+ private _tokens?: OAuthTokens | undefined;
11
+ private _codeVerifier?: string | undefined;
12
+ private _redirect: (url: URL) => Promise<void>;
13
+
14
+ constructor(redirectUrl: URL,
15
+ redirect: (url: URL) => Promise<void>) {
16
+ this._redirectUrl = redirectUrl;
17
+ // TODO: May need to take this in dynamically to support variation
18
+ // amongst MCP servers
19
+ // this._clientMetadata = clientMetadata;
20
+ this._redirect = redirect;
21
+ }
22
+
23
+ get redirectUrl(): string | URL {
24
+ return this._redirectUrl
25
+ }
26
+
27
+ get clientMetadata(): OAuthClientMetadata {
28
+ return {
29
+ redirect_uris: [ this._redirectUrl.toString() ],
30
+ scope: "mcp:tools",
31
+ token_endpoint_auth_method: "client_secret_post",
32
+ grant_types: [ "authorization_code", "refresh_token" ],
33
+ response_types: [ "code" ],
34
+ client_name: "Test Web OAuth Client",
35
+ client_uri: undefined, // TODO: Add client URI
36
+
37
+ // JWKS auth
38
+ jwks_uri: undefined,
39
+ jwks: undefined,
40
+
41
+ // Other identifying fields
42
+ logo_uri: undefined, // TODO: Add logo URI
43
+ tos_uri: undefined, // TODO: Add TOS URI
44
+ policy_uri: undefined, // TODO: Add policy URI
45
+ contacts: undefined, // TODO: Add contacts
46
+ software_id: undefined, // TODO: Add software ID
47
+ software_version: "0.1.0", // TODO: Add software version
48
+ software_statement: undefined, // TODO: Add software statement
49
+ }
50
+ }
51
+
52
+ // TODO: State parameters are a good thing?
53
+ // state?(): string | Promise<string> {
54
+ // throw new Error("Method not implemented.");
55
+ // }
56
+
57
+ clientInformation(): OAuthClientInformation | undefined |
58
+ Promise<OAuthClientInformation | undefined> {
59
+ return this._clientInformation;
60
+ }
61
+
62
+ saveClientInformation?(clientInformation: OAuthClientInformationFull): void | Promise<void> {
63
+ this._clientInformation = clientInformation;
64
+ }
65
+
66
+ tokens(): OAuthTokens | undefined | Promise<OAuthTokens | undefined> {
67
+ return this._tokens;
68
+ }
69
+ saveTokens(tokens: OAuthTokens): void | Promise<void> {
70
+ this._tokens = tokens;
71
+ }
72
+
73
+ redirectToAuthorization(authorizationUrl: URL): void | Promise<void> {
74
+ this._redirect(authorizationUrl);
75
+ }
76
+
77
+ codeVerifier(): string | Promise<string> {
78
+ if (!this._codeVerifier) {
79
+ throw new Error("Code verifier not set.");
80
+ }
81
+
82
+ return this._codeVerifier;
83
+ }
84
+ saveCodeVerifier(codeVerifier: string): void | Promise<void> {
85
+ this._codeVerifier = codeVerifier;
86
+ }
87
+
88
+ // addClientAuthentication?(headers: Headers, params: URLSearchParams, url: string | URL, metadata?: AuthorizationServerMetadata): void | Promise<void> {
89
+ // throw new Error("Method not implemented.");
90
+ // }
91
+ // validateResourceURL?(serverUrl: string | URL, resource?: string): Promise<URL | undefined> {
92
+ // throw new Error("Method not implemented.");
93
+ // }
94
+ // invalidateCredentials?(scope: "all" | "client" | "tokens" | "verifier"): void | Promise<void> {
95
+ // throw new Error("Method not implemented.");
96
+ // }
97
+ };
98
+
99
+ // NextJs blows this shit away while chunking
100
+ // const mcpAuthProviders = new Map<URL, WebOAuthClientProvider>();
101
+ type McpAuthProvidersStore = Map<string, WebOAuthClientProvider>;
102
+
103
+ type MCPActiveAuthFlow = {
104
+ clientName: string,
105
+ url: URL,
106
+ transport: StreamableHTTPClientTransport,
107
+ mcpClient: Client
108
+ };
109
+
110
+ // NextJs blows this shit away while chunking
111
+ //const mcpAuthFlows = new Map<string, MCPActiveAuthFlow>();
112
+ type McpAuthFlowsStore = Map<string, MCPActiveAuthFlow>;
113
+
114
+ // Extend globalThis to include some extra state
115
+ declare global {
116
+ var __mcpAuthFlows: McpAuthFlowsStore | undefined;
117
+ var __mcpProviders: McpAuthProvidersStore | undefined;
118
+ }
119
+
120
+ const mcpProviders: McpAuthProvidersStore = (globalThis.__mcpProviders
121
+ ?? (globalThis.__mcpProviders = new Map<string, WebOAuthClientProvider>())) as McpAuthProvidersStore;
122
+
123
+ const mcpAuthFlows: McpAuthFlowsStore = (globalThis.__mcpAuthFlows
124
+ ?? (globalThis.__mcpAuthFlows = new Map<string, MCPActiveAuthFlow>())) as McpAuthFlowsStore;
125
+
126
+ export const hasAuthProvider = (mcpUrl: URL): boolean => {
127
+ const url = mcpUrl.toString()
128
+ if (!mcpProviders.has(url)) {
129
+ return false
130
+ }
131
+
132
+ return true
133
+ }
134
+
135
+ export const getAuthProvider = (mcpUrl: URL): any => {
136
+ const url = mcpUrl.toString();
137
+ if (!mcpProviders.has(url)) {
138
+ // throw new Error("No auth provider found for MCP URL: " + mcpUrl);
139
+ return null
140
+ }
141
+
142
+ return mcpProviders.get(url) as WebOAuthClientProvider;
143
+ };
144
+
145
+ export const setupAuthProvider = async (mcpClientName: string, mcpUrl: URL,
146
+ version: string = "0.0.1"): Promise<URL | undefined> =>
147
+ {
148
+ console.log("setupAuthProvider");
149
+
150
+ // TODO: Generate unique cryptographically secure flow IDs to prevent auth flow collisions
151
+ const flowId = "abc123";
152
+
153
+ let redirectUrl = undefined;
154
+
155
+ // Create a new provider
156
+ const provider = new WebOAuthClientProvider(
157
+ // new URL(`http://localhost:3000/api/v1/mcp/auth/${flowId}`),
158
+ new URL(`http://localhost:${process.env.PORT}/api/mcp/auth`),
159
+ async (url: URL) => {
160
+ console.debug(`Redirecting to: ${url}`);
161
+
162
+ // Closure; we'll await later and return this
163
+ redirectUrl = url;
164
+ }
165
+ );
166
+
167
+ // Trigger the authentication flow by attempting to connect
168
+ const transport = new StreamableHTTPClientTransport(mcpUrl, {
169
+ authProvider: provider,
170
+ });
171
+
172
+ const mcpClient = new Client({
173
+ name: mcpClientName,
174
+ version,
175
+ }, { capabilities: {} });
176
+
177
+ // Persist some stateful bits
178
+ // TODO: Consider how to serialize these in a serverless manner and determine the extent
179
+ // to which they even need to be serialized
180
+ mcpProviders.set(mcpUrl.toString(), provider)
181
+ mcpAuthFlows.set(flowId, {
182
+ clientName: mcpClientName,
183
+ url: mcpUrl,
184
+ transport,
185
+ mcpClient
186
+ });
187
+
188
+ try {
189
+ await mcpClient.connect(transport);
190
+
191
+ // Authentication was successful, apparently
192
+ }
193
+ catch (error) {
194
+ if (error instanceof UnauthorizedError) {
195
+ // Expected failure; authentication is in progress (presumably); return the redirect
196
+ if (redirectUrl) {
197
+ return redirectUrl
198
+ }
199
+
200
+ throw new Error("Expected a redirect URL to be generated.");
201
+ }
202
+ else {
203
+ console.error("Error establishing initial MCP connection:", error);
204
+ throw error;
205
+ }
206
+ }
207
+ finally {
208
+ // Close the connection - for now
209
+ await mcpClient.close();
210
+ }
211
+ }
212
+
213
+ export const handleMCPAuthCallback = async (req: any, res: any) => {
214
+ console.log("handleMCPAuthCallback");
215
+ const { code } = req.query
216
+ if (!code) {
217
+ res.status(400).send('Missing code')
218
+ return
219
+ }
220
+
221
+ // TODO: Parameterize this
222
+ if (!mcpAuthFlows.has("abc123")) {
223
+ res.status(400).send('No active authentication flow found.')
224
+ return
225
+ }
226
+
227
+ const { url, clientName, transport, mcpClient } = mcpAuthFlows.get("abc123") as MCPActiveAuthFlow;
228
+
229
+ // TODO: check if we need to reuse the same transport to do this
230
+ // ... upon reviewing source, I don't think we do.
231
+ // Just need persisted clientInformation in the provider.
232
+ await transport.finishAuth(code)
233
+
234
+ console.log('TEST 3: ' + JSON.stringify(mcpProviders.get(url.toString())))
235
+
236
+ // Try to connect to confirm it is setup; note: need a new transport
237
+ const testTransport = new StreamableHTTPClientTransport(url, {
238
+ authProvider: mcpProviders.get(url.toString())
239
+ })
240
+
241
+ try {
242
+ await mcpClient.connect(testTransport);
243
+ }
244
+ catch (error) {
245
+ console.error("Error finalizing authentication for MCP connection:", error);
246
+ throw error;
247
+ }
248
+
249
+ // res.send('Authentication complete. You can close this window.')
250
+ res.redirect('/')
251
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "outDir": "./build",
5
+ "rootDir": "./src",
6
+ "jsx": "react",
7
+ "moduleResolution": "node",
8
+ "module": "es2020",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true
13
+ },
14
+ "include": ["src/server/**/**/*", "declarations.d.ts"],
15
+ "exclude": ["node_modules"]
16
+ }
@@ -0,0 +1,187 @@
1
+ <!DOCTYPE html>
2
+ <html dir="ltr" lang="en-US">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
6
+ <meta http-equiv="X-UA-Compatible" content="ie=edge">
7
+ <meta http-equiv="Content-Language" content="en" />
8
+
9
+ <title>Vinely</title>
10
+ <link rel="shortcut icon" href="https://lh3.googleusercontent.com/MpnmfR3zYPTojA03QbhGgwIHucmH5s4G2vbIlADYhODL488MQt_BuVztv9LYmJbhwkScYN0DIXuh4d10YMsLbWXT">
11
+ <link rel="stylesheet" href="{{{CDN}}}/css/globals-2.css" />
12
+ <link rel="stylesheet" href="{{{CDN}}}/dist/css/katex.min.css" />
13
+ <style>
14
+ .m-auto {
15
+ margin: auto;
16
+ }
17
+
18
+ .fw-600 {
19
+ font-weight: 600;
20
+ }
21
+
22
+ .reference-container {
23
+ transition: all 0.4s linear;
24
+ background: #1f2121;
25
+ width: 60%;
26
+ border-radius: 12px;
27
+ padding: 20px;
28
+ line-height: 20px;
29
+ border-bottom: 1px solid #2b2c2c;
30
+ }
31
+
32
+ .header {
33
+ position: fixed;
34
+ width: -webkit-fill-available;
35
+ width: -moz-available;
36
+ z-index: 1;
37
+ background: rgba(0,0,0,0);
38
+ backdrop-filter: blur(10px);
39
+ border-bottom: 1px solid #191a1a;
40
+ transition: all 0.2s linear;
41
+ /* box-shadow: 0px -9px 12px 8px #888888; */
42
+ }
43
+
44
+ .border-border {
45
+ border-color: #ece6e70d !important;
46
+ }
47
+
48
+ textarea {
49
+ color: #e8e8e3;
50
+ font-weight: 200;
51
+ background-color: #1f2121;
52
+ padding-top: 10px;
53
+ border-top-right-radius: 16px;
54
+ border-top-left-radius: 16px;
55
+ }
56
+
57
+ .text-default {
58
+ color: '#e8e8e3'
59
+ }
60
+
61
+ .color-accent {
62
+ color: #94ccff !important;
63
+ }
64
+
65
+ .bg-sidebar {
66
+ background: #1f2121 !important;
67
+ }
68
+
69
+ .text-center {
70
+ text-align: center;
71
+ }
72
+
73
+ .sidebar-list {
74
+ font-size: 14px;
75
+ line-height: 1.7;
76
+ }
77
+
78
+ .card-preview {
79
+ width: 100%;
80
+ height: 100%;
81
+ background: #1f2121;
82
+ border-radius: 12px;
83
+ border: 1px solid #ece6e70d;
84
+ color: #fff;
85
+ padding: 9px;
86
+ overflow: hidden;
87
+ transition: all 0.25s linear;
88
+ }
89
+
90
+ .card-preview:hover {
91
+ border: 1px solid #666;
92
+ }
93
+
94
+ .card-preview h5 {
95
+ font-size: 14px;
96
+ margin-top: 6px;
97
+ }
98
+
99
+ .card-preview p {
100
+ font-size: 12px;
101
+ font-weight: 100;
102
+ line-height: 1.7;
103
+ }
104
+
105
+ .card-preview hr {
106
+ border-color: #ece6e70d;
107
+ margin-top: 16px;
108
+ margin-bottom: 8px;
109
+ }
110
+
111
+ .card-preview small {
112
+ font-size: 12px;
113
+ font-weight: 200;
114
+ color: #999;
115
+ }
116
+
117
+ .thread-preview {
118
+ height: 256px;
119
+ transition: all 0.25s ease-in-out;
120
+ cursor: pointer;
121
+ }
122
+
123
+ .markdown {
124
+ width: 100%;
125
+ padding: 8rem 14rem;
126
+ color: #fff;
127
+ }
128
+
129
+ .markdown p {
130
+ margin-bottom: 24px;
131
+ }
132
+
133
+ .markdown li {
134
+ margin-bottom: 24px;
135
+ }
136
+
137
+ .markdown small {
138
+ opacity: 0.5;
139
+ }
140
+
141
+ .message-user {
142
+ display: flex;
143
+ flex-direction: row-reverse;
144
+ }
145
+
146
+ .message-user p {
147
+ background: #333;
148
+ padding: 16px;
149
+ border-radius: 12px;
150
+ }
151
+
152
+ .effect-shine {
153
+ mask-image: linear-gradient(-75deg, rgba(0,0,0,.6) 30%, #000 50%, rgba(0,0,0,.6) 70%);
154
+ -webkit-mask-image: linear-gradient(-75deg, rgba(0,0,0,.6) 30%, #000 50%, rgba(0,0,0,.6) 70%);
155
+ mask-size: 200%;
156
+ -webkit-mask-size: 200%;
157
+ animation: shine 2s infinite;
158
+ }
159
+
160
+ @-webkit-keyframes shine {
161
+ from {
162
+ -webkit-mask-position: 150%;
163
+ }
164
+
165
+ to {
166
+ -webkit-mask-position: -50%;
167
+ }
168
+ }
169
+
170
+ @keyframes shine {
171
+ from {
172
+ mask-position: 150%;
173
+ }
174
+
175
+ to {
176
+ mask-position: -50%;
177
+ }
178
+ }
179
+ </style>
180
+ </head>
181
+
182
+ <body style="background: #191a1a;">
183
+ <div id="root" style="background: #191a1a;"></div>
184
+
185
+ {{>imports}}
186
+ </body>
187
+ </html>
@@ -0,0 +1,3 @@
1
+ <script>window.__CONTEXT__ = {{{context}}}</script>
2
+ <script type="text/javascript" src="/dist/dev/commons.js"></script>
3
+ <script type="text/javascript" src="/dist/dev/app.js"></script>