Skip to content

Commit 6e1c5b4

Browse files
committed
Dia 3 - Octubre 2024
1 parent 0c0b935 commit 6e1c5b4

32 files changed

Lines changed: 8846 additions & 0 deletions
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Nuxt dev/build outputs
2+
.output
3+
.data
4+
.nuxt
5+
.nitro
6+
.cache
7+
dist
8+
9+
# Node dependencies
10+
node_modules
11+
12+
# Logs
13+
logs
14+
*.log
15+
16+
# Misc
17+
.DS_Store
18+
.fleet
19+
.idea
20+
21+
# Local env files
22+
.env
23+
.env.*
24+
!.env.example
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"workbench.colorCustomizations": {
3+
"activityBar.activeBackground": "#fa368b",
4+
"activityBar.background": "#fa368b",
5+
"activityBar.foreground": "#e7e7e7",
6+
"activityBar.inactiveForeground": "#e7e7e799",
7+
"activityBarBadge.background": "#2a5d02",
8+
"activityBarBadge.foreground": "#e7e7e7",
9+
"commandCenter.border": "#e7e7e799",
10+
"sash.hoverBorder": "#fa368b",
11+
"statusBar.background": "#f7066e",
12+
"statusBar.foreground": "#e7e7e7",
13+
"statusBarItem.hoverBackground": "#fa368b",
14+
"statusBarItem.remoteBackground": "#f7066e",
15+
"statusBarItem.remoteForeground": "#e7e7e7",
16+
"titleBar.activeBackground": "#f7066e",
17+
"titleBar.activeForeground": "#e7e7e7",
18+
"titleBar.inactiveBackground": "#f7066e99",
19+
"titleBar.inactiveForeground": "#e7e7e799"
20+
},
21+
"peacock.color": "#f7066e"
22+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# Nuxt 3 Minimal Starter
2+
3+
Look at the [Nuxt 3 documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
4+
5+
## Setup
6+
7+
Make sure to install the dependencies:
8+
9+
```bash
10+
# npm
11+
npm install
12+
13+
# pnpm
14+
pnpm install
15+
16+
# yarn
17+
yarn install
18+
19+
# bun
20+
bun install
21+
```
22+
23+
## Development Server
24+
25+
Start the development server on `http://localhost:3000`:
26+
27+
```bash
28+
# npm
29+
npm run dev
30+
31+
# pnpm
32+
pnpm run dev
33+
34+
# yarn
35+
yarn dev
36+
37+
# bun
38+
bun run dev
39+
```
40+
41+
## Production
42+
43+
Build the application for production:
44+
45+
```bash
46+
# npm
47+
npm run build
48+
49+
# pnpm
50+
pnpm run build
51+
52+
# yarn
53+
yarn build
54+
55+
# bun
56+
bun run build
57+
```
58+
59+
Locally preview production build:
60+
61+
```bash
62+
# npm
63+
npm run preview
64+
65+
# pnpm
66+
pnpm run preview
67+
68+
# yarn
69+
yarn preview
70+
71+
# bun
72+
bun run preview
73+
```
74+
75+
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { expect, test, describe } from 'vitest';
2+
import { render } from '@testing-library/vue';
3+
4+
import ChatAvatar from './ChatAvatar.vue';
5+
import type { ChatUsers } from '~/types';
6+
7+
describe('ChatAvatar', () => {
8+
const renderComponent = ({ sender }: { sender: 'user' | 'ai' }) => {
9+
const component = render(ChatAvatar, {
10+
props: { sender },
11+
});
12+
13+
return component;
14+
};
15+
16+
test.each([
17+
[{ sender: 'user' as ChatUsers }, 'User icon'],
18+
[{ sender: 'ai' as ChatUsers }, 'Ollama logo'],
19+
])(
20+
'ChatAvatar renders the correct icon when the sender %s',
21+
({ sender }, expectedLabel) => {
22+
const component = renderComponent({ sender });
23+
24+
expect(component.findByLabelText(expectedLabel)).toBeTruthy();
25+
}
26+
);
27+
});
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<template>
2+
<div>
3+
<ChatOllamaIcon aria-label="Ollama logo" class="size-16 p-2 flex-shrink-0 rounded-full bg-green-400"
4+
v-if="sender === 'ai'" />
5+
<ChatUserIcon aria-label="User icon" class="size-16 p-2 flex-shrink-0 rounded-full bg-gray-400"
6+
v-if="sender === 'user'" />
7+
</div>
8+
</template>
9+
10+
<script setup lang="ts">
11+
import type { ChatRecord } from '~/types';
12+
13+
defineProps<{
14+
sender: ChatRecord['sender'];
15+
}>();
16+
</script>
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<template>
2+
<div class="flex gap-3 justify-between">
3+
<div class="grid gap-3 w-full grid-cols-[auto_1fr]">
4+
<ChatAvatar :sender="record.sender" />
5+
6+
<div class="prose prose-invert lg:prose-xl max-w-full has-[:only-child]:content-center"
7+
v-html="formattedMessage.html" />
8+
</div>
9+
10+
<time class="text-sm text-slate-400">{{ formattedTime }}</time>
11+
</div>
12+
</template>
13+
14+
<script setup lang="ts">
15+
import type { ChatRecord } from '~/types';
16+
17+
const props = defineProps<{
18+
record: ChatRecord;
19+
}>();
20+
21+
const { parse } = useMarkdown();
22+
23+
const formattedMessage = computed(() => {
24+
return parse(props.record.message);
25+
});
26+
27+
const formattedTime = computed(() => {
28+
const date = new Date(props.record.timestamp);
29+
30+
return new Intl.DateTimeFormat('es', {
31+
hour: 'numeric',
32+
minute: 'numeric',
33+
}).format(date);
34+
});
35+
36+
37+
</script>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<template>
2+
<img src="/ollama.png" class="block object-contain" alt="ollama logo" />
3+
</template>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { expect, test } from 'vitest';
2+
import { render } from '@testing-library/vue';
3+
4+
import ChatUserIcon from './ChatUserIcon.vue';
5+
6+
test('ChatUserIcon has the expected structure', () => {
7+
const component = render(ChatUserIcon);
8+
9+
expect(component.html()).toMatchInlineSnapshot(`
10+
"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
11+
<path d="M843.283 870.116c-8.439-140.516-104.296-257.423-233.908-297.15C687.88 536.273 742.4 456.533 742.4 364.089c0-127.242-103.159-230.4-230.4-230.4s-230.4 103.158-230.4 230.4c0 92.444 54.519 172.184 133.12 208.877-129.612 39.727-225.47 156.634-233.908 297.15-.664 10.903 7.964 20.195 18.963 20.195 9.955 0 18.3-7.775 18.963-17.73C227.745 718.507 355.65 596.385 512 596.385s284.255 122.122 293.357 276.196c.569 9.955 8.913 17.73 18.963 17.73 10.999 0 19.627-9.292 18.963-20.195zM319.526 364.089c0-106.288 86.187-192.474 192.474-192.474S704.474 257.8 704.474 364.089c0 106.287-86.187 192.474-192.474 192.474s-192.474-86.187-192.474-192.474z"></path>
12+
</svg>"
13+
`);
14+
});
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<template>
2+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
3+
<path
4+
d="M843.283 870.116c-8.439-140.516-104.296-257.423-233.908-297.15C687.88 536.273 742.4 456.533 742.4 364.089c0-127.242-103.159-230.4-230.4-230.4s-230.4 103.158-230.4 230.4c0 92.444 54.519 172.184 133.12 208.877-129.612 39.727-225.47 156.634-233.908 297.15-.664 10.903 7.964 20.195 18.963 20.195 9.955 0 18.3-7.775 18.963-17.73C227.745 718.507 355.65 596.385 512 596.385s284.255 122.122 293.357 276.196c.569 9.955 8.913 17.73 18.963 17.73 10.999 0 19.627-9.292 18.963-20.195zM319.526 364.089c0-106.288 86.187-192.474 192.474-192.474S704.474 257.8 704.474 364.089c0 106.287-86.187 192.474-192.474 192.474s-192.474-86.187-192.474-192.474z" />
5+
</svg>
6+
</template>
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<template>
2+
<section class="grid grid-rows-[1fr,200px] h-full px-3 pt-3 pb-5 gap-4 overflow-hidden">
3+
<ChatTheOutput :conversation="conversation" />
4+
5+
<form @submit.prevent="onSubmit" class="flex justify-end gap-4">
6+
<textarea class="block w-full resize-none rounded-md p-2 bg-gray-700 border border-1 text-white"
7+
@keydown="onKeyDown" @keyup="onKeyUp" autofocus v-model="message"></textarea>
8+
9+
<button type="button" v-if="hasAbort" class="rounded-md hover:bg-gray-700 hover:text-white p-3 self-end"
10+
@click="abort">Abort</button>
11+
12+
<button class="rounded-md hover:bg-gray-700 hover:text-white p-3 self-end">
13+
Send
14+
</button>
15+
</form>
16+
</section>
17+
</template>
18+
19+
<script setup lang="ts">
20+
import { ref } from 'vue';
21+
const message = ref('');
22+
23+
const isShiftPressed = ref(false);
24+
25+
const onKeyDown = (event: KeyboardEvent) => {
26+
if (event.key === 'Shift') {
27+
isShiftPressed.value = true;
28+
}
29+
};
30+
31+
const onKeyUp = (event: KeyboardEvent) => {
32+
if (event.key === 'Shift') {
33+
isShiftPressed.value = false;
34+
}
35+
36+
if (event.key === 'Enter' && !isShiftPressed.value) {
37+
onSubmit();
38+
39+
return;
40+
}
41+
};
42+
43+
const ai = useAI();
44+
45+
const route = useRoute();
46+
47+
const chatHistory = useChatHistory();
48+
const chatId = computed(() => route.params.chatId as string)
49+
const conversation = computed(() => chatHistory.getConversation(chatId.value));
50+
51+
let abort: () => void;
52+
const hasAbort = ref(false);
53+
const onSubmit = async () => {
54+
const chatRecord = chatHistory.messageFactory(message.value, 'user');
55+
chatHistory.addMessage(chatRecord, chatId.value);
56+
message.value = '';
57+
58+
const response = await ai.getCompletion(conversation.value,);
59+
abort = response.abort
60+
hasAbort.value = true;
61+
62+
await nextTick()
63+
64+
const date = Date.now();
65+
66+
for await (const part of response) {
67+
const chatRecord = chatHistory.messageFactory(part.message.content, 'ai', date);
68+
chatHistory.updateAssistantMessage(chatRecord, chatId.value);
69+
}
70+
abort = () => { };
71+
hasAbort.value = false;
72+
};
73+
</script>

0 commit comments

Comments
 (0)