Skip to content

Commit

Permalink
etl pipeline to postgres with prisma to frontend
Browse files Browse the repository at this point in the history
  • Loading branch information
mklarqvist committed May 9, 2023
1 parent fc7e929 commit b31ca14
Show file tree
Hide file tree
Showing 33 changed files with 2,167 additions and 336 deletions.
2 changes: 1 addition & 1 deletion google-magi-clone/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Google "Magi" clone with Nuxt3 + Vuetify
# Google "Magi" clone

Google made a recent announcement (as of yesterday when writing this) unveiling a new project called "Magi." This initiative aims to integrate advanced large-language models with their search capabilities. As there is no public demo available for review, I've decided to create a clone inspired by the image provided below:

Expand Down
9 changes: 9 additions & 0 deletions movie-embeddings/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Dockerfile
FROM node:19-alpine
WORKDIR /app
RUN apk add --update --no-cache libc6-compat
COPY package*.json ./
RUN npm install
EXPOSE 3000

CMD ["npm", "run", "dev", "--"]
7 changes: 0 additions & 7 deletions movie-embeddings/app.vue

This file was deleted.

2 changes: 1 addition & 1 deletion movie-embeddings/assets/main.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
$family: "Inter var", "Inter", apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans,
$family: "Roboto var", "Roboto", apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans,
Ubuntu, Cantarell, "Helvetica Neue", sans-serif;

@use "vuetify" with (
Expand Down
45 changes: 45 additions & 0 deletions movie-embeddings/components/app-footer.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<template>
<div>
<v-footer :elevation="0" class="mt-16">
<v-container class="py-16">

<div class="d-flex">
<div style="width:150px">
<nuxt-link to="https://github.com/2x-ai"><img src="/assets/2x_logo_white.svg" alt="2xAI logo"
height="48" /></nuxt-link>
</div>
<div class="me-auto pl-4">
<nuxt-link to="https://twitter.com/marcusklarqvist"><v-icon>mdi-twitter</v-icon></nuxt-link>
<nuxt-link to="https://twitter.com/twoxAI"><v-icon>mdi-twitter</v-icon></nuxt-link>
<nuxt-link to="https://github.com/2x-ai"><v-icon>mdi-github</v-icon></nuxt-link>
<nuxt-link to="https://github.com/mklarqvist"><v-icon>mdi-github</v-icon></nuxt-link>
</div>
</div>
Data sources are: <nuxt-link to="https://www.themoviedb.org/">TMDb</nuxt-link>, <nuxt-link
to="https://www.imdb.com/">IMDb</nuxt-link>, <nuxt-link to="https://www.wikipedia.org/">Wikipedia</nuxt-link>,
<nuxt-link to="https://themoviespoiler.com/">The Movie Spoiler</nuxt-link>. Copyrights and all other rights belong
to their respective owner.
</v-container>
</v-footer>
</div>
</template>

<script setup>
</script>

<style>
.v-footer {
color: rgba(255, 255, 255, 0.7);
background-color: black;
}
.v-footer a {
text-decoration: underline;
color: rgba(255, 255, 255, 0.7);
}
.v-footer a:hover {
opacity: 1;
}
</style>
56 changes: 56 additions & 0 deletions movie-embeddings/components/app-header.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<template>
<div>
<v-app-bar style="color:white;"
:style="props.color ? `background-color: rgba(${props.color.DarkVibrant.rgb[0]},${props.color.DarkVibrant.rgb[1]},${props.color.DarkVibrant.rgb[2]}, 1)` : 'inherit'">
<v-app-bar-nav-icon>
<img src="/assets/movie_2x_logo.svg" height="36" />
</v-app-bar-nav-icon>
<v-app-bar-title style="flex: inherit;margin-inline-start:8px;-webkit-margin-start: 8px;" class="mr-4">
<nuxt-link to="/">FlickFinder</nuxt-link>
</v-app-bar-title>

<!-- Search bar -->
<v-text-field v-model="search" hide-details prepend-inner-icon="mdi-magnify" single-line placeholder="Search"
@keyup.enter="searchMovie(search)"></v-text-field>


<v-spacer></v-spacer>

<!-- Links -->
<nuxt-link to="https://www.github.com/mklarqvist">
<v-btn icon>
<v-icon>mdi-github</v-icon>
</v-btn>
</nuxt-link>

<nuxt-link to="https://www.twitter.com/marcusklarqvist">
<v-btn icon>
<v-icon>mdi-twitter</v-icon>
</v-btn>
</nuxt-link>

</v-app-bar>
</div>
</template>

<script setup>
const props = defineProps({
color: {
type: Object, // A colorPalette
required: false
},
});
const search = ref('');
const searchMovie = (query) => {
if (query) {
navigateTo(`/search/${query}`)
}
}
</script>

<style scoped>
.v-toolbar {
background-color: black;
}
</style>
139 changes: 139 additions & 0 deletions movie-embeddings/components/dialog-text-comparison.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
<template>
<!-- dialog text similarity -->
<div class="text-center">
<v-dialog v-model="props.dialogSynopsis" width="auto" @keydown.esc="togglePanel()">
<v-card>
<div style="position: relative;">
<v-row style="border: 1px solid rgba(0,0,0,0.2);margin: 20px 0;">
<v-col md="2" cols="3" style="border-right: 1px solid rgba(0,0,0,0.2);padding: 0;">
<v-img class="poster lazyload lazyloaded"
:src="`https://www.themoviedb.org/t/p/w300_and_h450_bestv2/${props.referencePosterPath}`"
:data-src="`https://www.themoviedb.org/t/p/w300_and_h450_bestv2/${props.referencePosterPath}`"
:data-srcset="`https://www.themoviedb.org/t/p/w300_and_h450_bestv2/${props.referencePosterPath} 1x, https://www.themoviedb.org/t/p/w600_and_h900_bestv2/${props.referencePosterPath} 2x`"
:srcset="`https://www.themoviedb.org/t/p/w300_and_h450_bestv2/${props.referencePosterPath} 1x, https://www.themoviedb.org/t/p/w600_and_h900_bestv2/${props.referencePosterPath} 2x`"
:alt="title" width="100%">
<template v-slot:placeholder>
<div class="d-flex align-center justify-center fill-height">
<v-progress-circular color="grey-lighten-4" indeterminate></v-progress-circular>
</div>
</template>
</v-img>
</v-col>
<v-col md="4" cols="3" style="border-right: 1px solid rgba(0,0,0,0.2);" class="pa-4">
<h3>{{ props.title }}</h3>
<div class="mb-2">Matching synopsis section (could be truncated):</div>
<word-animator :inputText="props.referenceText"></word-animator>
<div class="mt-2">
Source: <img src="/assets/imdb_logo.png" alt="IMDb logo" height="15">
</div>
</v-col>
<v-col md="4" cols="3" style="border-right: 1px solid rgba(0,0,0,0.2);" class="pa-4">
<h3>{{ props.queryTitle }}</h3>
<div class="mb-2">Matching synopsis section (could be truncated):</div>
<word-animator :inputText="props.queryText"></word-animator>
<div class="mt-2">
Source: <img src="/assets/imdb_logo.png" alt="IMDb logo" height="15">
</div>

</v-col>
<v-col md="2" cols="3" style="padding: 0;">
<nuxt-link :to="`/movie/${queryMovieId}`">
<v-img class="poster lazyload lazyloaded"
:src="`https://www.themoviedb.org/t/p/w300_and_h450_bestv2/${props.queryPosterPath}`"
:data-src="`https://www.themoviedb.org/t/p/w300_and_h450_bestv2/${props.queryPosterPath}`"
:data-srcset="`https://www.themoviedb.org/t/p/w300_and_h450_bestv2/${props.queryPosterPath} 1x, https://www.themoviedb.org/t/p/w600_and_h900_bestv2/${props.queryPosterPath} 2x`"
:srcset="`https://www.themoviedb.org/t/p/w300_and_h450_bestv2/${props.queryPosterPath} 1x, https://www.themoviedb.org/t/p/w600_and_h900_bestv2/${props.queryPosterPath} 2x`"
:alt="title" width="100%">
<template v-slot:placeholder>
<div class="d-flex align-center justify-center fill-height">
<v-progress-circular color="grey-lighten-4" indeterminate></v-progress-circular>
</div>
</template>
</v-img>
<v-btn style="
-webkit-transition: background 100ms,opacity 100ms;
transition: background 100ms,opacity 100ms;
border: none;
outline: none;
cursor: pointer;
line-height: 1;
color: #fff;
background: linear-gradient(145deg,#000,#333);
border-radius: 24px;
-webkit-letter-spacing: 1px;
-moz-letter-spacing: 1px;
-ms-letter-spacing: 1px;
letter-spacing: 1px;
font-size: 14px;
padding: 16px 20px;
height: 48px;
width: 100%;
border-radius: 0;
border-bottom-left-radius: 24px;
border-bottom-right-radius: 24px;
">
<v-icon class="mr-2">mdi-movie-filter-outline</v-icon>Visit</v-btn>
</nuxt-link>
</v-col>
</v-row>

<v-btn color="black" variant="text" @click="togglePanel()"
style="position: absolute;top:5px;right:5px;background-color: rgba(0,0,0,0.25)"><v-icon>mdi-close</v-icon></v-btn>

</div>
</v-card>
</v-dialog>
</div>
</template>

<script setup>
const props = defineProps({
title: {
type: String,
required: true
},
queryTitle: {
type: String,
required: true
},
queryPosterPath: {
type: String,
required: true
},
referencePosterPath: {
type: String,
required: true
},
dialogSynopsis: Boolean,
queryText: {
type: String,
required: true
},
referenceText: {
type: String,
required: true
},
referenceMovieId: {
type: Number,
required: true
},
queryMovieId: {
type: Number,
required: true
},
})
const emit = defineEmits(['closePanel'])
function togglePanel() {
console.log('clicked close panel')
emit('closePanel')
}
</script>

<style scoped>
.v-responsive__sizer {
padding-bottom: 100%;
}
</style>
83 changes: 83 additions & 0 deletions movie-embeddings/components/word-animator.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<template>
<div class="word-animator">
<span class="text-progress" :class="{'animate-[cursorBlink_600ms_ease-in-out_infinite_alternate]': isRunning, 'text-finished': isFinished}">{{ animatedWord }}</span>
</div>
</template>

<script setup>
import { watchEffect } from "vue";
const props = defineProps({
inputText: String,
});
const animatedWord = ref("");
const isRunning = ref(false)
const isFinished = ref(false)
const animateText = (text) => {
isRunning.value = true
isFinished.value = false
animatedWord.value = "";
const animationSpeed = 2; // 2 milliseconds per character
let charIndex = 0;
const intervalId = setInterval(() => {
animatedWord.value += text[charIndex];
charIndex++;
if (charIndex >= text.length) {
clearInterval(intervalId);
isRunning.value = false
isFinished.value = true
}
}, animationSpeed);
};
watchEffect(() => {
if (props.inputText) {
animateText(props.inputText);
}
});
</script>

<style lang="scss" scoped>
.text-progress {
white-space: pre-wrap;
font-size: 1rem;
line-height: 130%;
letter-spacing: 0;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
border-right: 12px solid #000;
background-color: rgba(0, 0, 0, 0.2);
transition: background-color .2s ease-in-out;
}
.animate-\[cursorBlink_600ms_ease-in-out_infinite_alternate\] {
-webkit-animation: cursorBlink .6s ease-in-out infinite alternate;
animation: cursorBlink .6s ease-in-out infinite alternate;
}
.text-finished {
border: none;
background-color: transparent;
}
@-webkit-keyframes cursorBlink {
0% {
border-right-color:initial
}
to {
border-right-color:transparent
}
}
@keyframes cursorBlink {
0% {
border-right-color:initial
}
to {
border-right-color:transparent
}
}
</style>
28 changes: 28 additions & 0 deletions movie-embeddings/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# docker-compose.yml
version: '3.9'

services:
nuxt:
build: .
container_name: nuxt-app
ports:
- '3000:3000'
depends_on:
- db
environment:
DATABASE_URL: postgresql://user:password@db:5432/dbname
volumes:
- .:/app
- /app/node_modules
db:
image: postgres:15.2-alpine
container_name: postgres-db
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: password
POSTGRES_DB: dbname
volumes:
- db-data:/var/lib/postgresql/data

volumes:
db-data:
2 changes: 1 addition & 1 deletion movie-embeddings/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export default defineNuxtConfig({
],
googleFonts: {
families: {
"Inter": {
"Roboto": {
wght: [400, 500, 600, 700, 800],
ital: [400, 500, 600, 700, 800],
},
Expand Down
Loading

0 comments on commit b31ca14

Please sign in to comment.