Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
robvanvolt committed Sep 7, 2024
0 parents commit 08b3de1
Show file tree
Hide file tree
Showing 36 changed files with 2,400 additions and 0 deletions.
12 changes: 12 additions & 0 deletions .example.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
### SERVER URL
SERVER_URL="http://...:8001"
SERVER_API_KEY="..."

### SPEECH TO TEXT
GROQ_API_KEY="gsk_..."
GROQ_API_MODEL="whisper-large-v3"

### LARGE LANGUAGE MODELS
API_URL="https://.../v1/chat/completions"
API_KEY="api-key"
API_MODEL="gpt-4o"
37 changes: 37 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Deploy
on:
push:
branches:
- 'main'
- 'preview'
pull_request:
branches:
- 'main'
- 'preview'

jobs:
deploy:
name: Deploy
runs-on: ubuntu-latest

permissions:
id-token: write # Needed for auth with Deno Deploy
contents: read # Needed to clone the repository

steps:
- name: Clone repository
uses: actions/checkout@v4

- name: Install Deno
uses: denoland/setup-deno@v1
with:
deno-version: v1.x

- name: Build step
run: "deno task build" # 📝 Update the build command(s) if necessary

- name: Upload to Deno Deploy
uses: denoland/deployctl@v1
with:
project: "school-bud-e" # 📝 Update the deploy project name if necessary
entrypoint: "./main.ts" # 📝 Update the entrypoint if necessary
13 changes: 13 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
.DS_Store
.vscode

# Fresh build directory
_fresh/
# npm dependencies
node_modules/
19 changes: 19 additions & 0 deletions License.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Copyright (c) 2024 LAION e.V.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
OR OTHER DEALINGS IN THE SOFTWARE./
83 changes: 83 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# School Bud-E 🎓🤖

![School Bud-E Banner](banner.png)

Welcome to School Bud-E, your AI-powered educational assistant! 🚀

[![Join us on Discord](https://img.shields.io/discord/823813159592001537?color=5865F2&logo=discord&logoColor=white)](https://discord.gg/xBPBXfcFHd)

## 🌟 Overview

School Bud-E is an intelligent and empathetic learning assistant designed to revolutionize the educational experience. Developed by [LAION](https://laion.ai) in collaboration with the ELLIS Institute Tübingen, Collabora, the Tübingen AI Center and the DFKI, School Bud-E focuses on empathy, natural interaction, and personalized learning.

## 🚀 Features (WIP)

- 💬 Real-time responses to student queries
- 🧠 Emotionally intelligent interactions
- 🔄 Continuous conversation context
- 👥 Multi-speaker and multi-language support
- 🖥️ Local operation on consumer-grade hardware
- 🔒 Privacy-focused design

## 🛠️ Technology Stack

- **Frontend**: Fresh framework (Preact-based)
- **Styling**: Tailwind CSS
- **Language Support**: Internationalization for English and German
- **AI Models**:
- Speech-to-Text: Whisper Large V3 (via Groq API)
- Large Language Model: GPT-4o or equivalent

## 🏗️ Project Structure

- `routes/`: Application routes
- `components/`: Reusable UI components
- `islands/`: Interactive components (Fresh islands)
- `internalization/`: Language-specific content
- `static/`: Static assets

## 🚀 Getting Started

1. Clone the repository:

```bash
git clone https://github.com/LAION-AI/school-bud-e.git
```

2. Install dependencies:

```bash
deno task start
```

3. Set up environment variables:
- Copy `.example.env` to `.env`
- Fill in the required API keys and endpoints

4. Run the development server:

```bash
deno task start
```

5. Open `http://localhost:8000` in your browser

## 🤝 Contributing

We welcome contributions to School Bud-E! Please join our [Discord server](https://discord.com/invite/eq3cAMZtCC) or contact us at <[email protected]> to get involved.

## 🚧 Experimental Demo Version

Please note that this is an early prototype application that may provide inaccurate answers or generate content that is not suitable for all audiences. We advise caution and encourage you to report any issues you encounter to us.

## 📄 License

This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.

## 🙏 Acknowledgements

Special thanks to LAION, ELLIS Institute Tübingen, Collabora, the Tübingen AI Center and the German Research Center for Artificial Intelligence (DFKI) for their contributions and support to this project.

---

Built with ❤️ for the future of education.
Binary file added banner.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
38 changes: 38 additions & 0 deletions components/ChatSubmitButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Button.tsx
import { JSX } from "preact";
import { IS_BROWSER } from "$fresh/runtime.ts";

export function ChatSubmitButton(props: JSX.HTMLAttributes<HTMLButtonElement>) {
// Destructure `class` from props to apply alongside Tailwind classes
const { class: className, ...buttonProps } = props;

return (
<button
{...buttonProps}
// Spread the rest of the buttonProps here
disabled={!IS_BROWSER || props.disabled}
class={`absolute right-3 bottom-3 disabled:opacity-50 disabled:cursor-not-allowed rounded-md p-2 ${
props.disabled ? "bg-gray-100" : "bg-gray-400"
} ${className}`} // Apply external class here
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class={`icon icon-tabler icons-tabler-outline icon-tabler-arrow-narrow-up ${
props.disabled ? "text-gray-400" : "text-white"
}`}
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M12 5v14" />
<path d="M16 9l-4 -4l-4 4" />
</svg>
</button>
);
}
182 changes: 182 additions & 0 deletions components/ChatTemplate.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import { useEffect, useState } from "preact/hooks";

import { chatTemplateContent } from "../internalization/content.ts";

function ChatTemplate(
{
lang,
parentImages,
messages,
readAlways,
audioFileDict,
onRefreshAction,
onSpeakAtGroupIndexAction,
onImageChange,
onToggleReadAlwaysAction,
}: {
lang: string;
parentImages: Image[];
messages: Message[];
isComplete: boolean;
readAlways: boolean;
audioFileDict: AudioFileDict;
onToggleReadAlwaysAction: () => void;
onSpeakAtGroupIndexAction: (groupIndex: number) => void;
onRefreshAction: (groupIndex: number) => void;
onEditAction: (groupIndex: number) => void;
onUploadActionToMessages: (uploadedMessages: Message[]) => void;
onImageChange: (images: Image[]) => void;
onTrashAction: () => void;
},
) {
const [images, setImages] = useState<Image[]>([]);
const [imageFiles, setImageFiles] = useState<Image[]>([]);

// deno-lint-ignore no-explicit-any
const deleteImage = (event: any) => {
const index = images.findIndex((image) =>
image.preview === event.target.src
);
const newImages = [...images];
const newImageFiles = [...imageFiles];
newImages.splice(index, 1);
newImageFiles.splice(index, 1);
setImages(newImages);
setImageFiles(newImageFiles);
onImageChange(newImageFiles);
};

useEffect(() => {
setImages(parentImages);
}, [parentImages]);

return (
<div
class={messages?.length === 0
? `bg-transparent`
: `chat-history flex flex-col space-y-4 p-4 mx-auto rounded-lg shadow bg-white/75`}
>
<button
class={`absolute top-0 left-0 m-4 text-xs align-middle text-gray-600 hover:text-gray-800 transition-colors`}
onClick={() => onToggleReadAlwaysAction()}
>
{readAlways ? chatTemplateContent[lang]["readOutText"] : chatTemplateContent[lang]["silent"]}
</button>
{messages?.map((item, groupIndex) => {
return (
<div
key={groupIndex}
class={`message-group flex flex-col ${
item.role === "user" ? "items-end" : "items-start"
}`}
>
<span
class={`text-sm font-semibold flex justify-center items-center ${
item.role === "user" ? "text-blue-600" : "text-gray-600"
}`}
>
{item.role === "user" ? "Du" : "School Bud-E"}
{item.role !== "user" && groupIndex !== 0 && (
<button onClick={() => onRefreshAction(groupIndex)}>
<svg
xmlns="http://www.w3.org/2000/svg"
style="margin-left: 0.5rem; width: 24px; height: 24px;"
viewBox="0 -960 960 960"
fill="grey"
>
<path d="M440-122q-121-15-200.5-105.5T160-440q0-66 26-126.5T260-672l57 57q-38 34-57.5 79T240-440q0 88 56 155.5T440-202v80Zm80 0v-80q87-16 143.5-83T720-440q0-100-70-170t-170-70h-3l44 44-56 56-140-140 140-140 56 56-44 44h3q134 0 227 93t93 227q0 121-79.5 211.5T520-122Z" />
</svg>
</button>
)}
{item.role !== "user" && (
<button onClick={() => onSpeakAtGroupIndexAction(groupIndex)}>
{!audioFileDict[groupIndex] ||
!audioFileDict[groupIndex].some((audioFile) => !audioFile.paused)
? (
<svg
xmlns="http://www.w3.org/2000/svg"
style="margin-left: 0.5rem; width: 24px; height: 24px;"
viewBox="0 -960 960 960"
fill="currentColor"
>
<path d="M560-131v-82q90-26 145-100t55-168q0-94-55-168T560-749v-82q124 28 202 125.5T840-481q0 127-78 224.5T560-131ZM120-360v-240h160l200-200v640L280-360H120Zm440 40v-322q47 22 73.5 66t26.5 96q0 51-26.5 94.5T560-320ZM400-606l-86 86H200v80h114l86 86v-252ZM300-480Z" />
</svg>
)
: (
<svg
xmlns="http://www.w3.org/2000/svg"
style="margin-left: 0.5rem; width: 24px; height: 24px;"
viewBox="0 -960 960 960"
fill="currentColor"
>
<path d="M320-320h320v-320H320v320ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z" />
</svg>
)}
</button>
)}
</span>
<div
class={`message mt-1 whitespace-pre-wrap [overflow-wrap:anywhere] ${
item.role === "user"
? "bg-blue-100 sm:ml-20 md:ml-40"
: "bg-gray-100 sm:mr-20 md:mr-40"
} p-3 rounded-lg ${
item.role === "user" ? "rounded-tr-none" : "rounded-tl-none"
} shadow`}
>
{typeof item.content === "string"
? <span>{item.content}</span>
: (
<span>
{typeof item.content[0] === "string"
? item.content.join("")
: (
<div>
{(item.content as unknown as {
"type": string;
"text": string;
"image_url": { url: string };
}[]).map((content, contentIndex) => {
if (content.type === "text") {
return (
<span key={contentIndex}>{content.text}</span>
);
} else if (content.type === "image_url") {
return (
<img
key={contentIndex}
src={content.image_url.url}
alt="User uploaded image"
class="max-w-full h-auto rounded-lg shadow-sm"
/>
);
}
})}
</div>
)}
</span>
)}
</div>
</div>
);
})}
{images.length > 0 && (
<div class="w-full flex justify-center">
<div class="p-2 flex flex-wrap max-w-xs gap-8">
{images.map((image, index) => (
<img
key={index}
src={image.image_url.url}
onClick={deleteImage}
alt={`Thumbnail ${index + 1}`}
class="w-32 h-32 object-cover rounded-lg shadow-xl bg-white/50 cursor-pointer hover:bg-red-500/50"
/>
))}
</div>
</div>
)}
</div>
);
}

export default ChatTemplate;
Loading

0 comments on commit 08b3de1

Please sign in to comment.