Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: update theme, add diarization #81

Merged
merged 4 commits into from
Sep 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 14 additions & 8 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
AZURE_OPENAI_ENDPOINT=url
AZURE_OPENAI_API_KEY=key
AZURE_OPENAI_GPT_DEPLOYMENT_NAME=name
AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME=name
AZURE_DOCUMENT_INTELLIGENCE_KEY=key
AZURE_DOCUMENT_INTELLIGENCE_ENDPOINT=endpoint

OPENAI_API_KEY=key

AZURE_OPENAI_ENDPOINT=secret
AZURE_OPENAI_API_KEY=secret
AZURE_OPENAI_GPT_DEPLOYMENT_NAME=secret
AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME=secret
AZURE_OPENAI_WHISPER_DEPLOYMENT_NAME=secret

AZURE_CREATE_DIARIZATION_ENDPOINT=secret

AZURE_DOCUMENT_INTELLIGENCE_KEY=secret
AZURE_DOCUMENT_INTELLIGENCE_ENDPOINT=secret

AWS_ACCESS_KEY=secret
AWS_SECRET_ACCESS_KEY=secret
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ node_modules
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
.vscode
test-results
1,688 changes: 918 additions & 770 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
"dependencies": {
"@aws-sdk/client-bedrock-runtime": "^3.624.0",
"@azure/openai": "^1.0.0-beta.12",
"@cmi-dair/skeleton-themes": "^0.1.1",
"@cmi-dair/skeleton-themes": "^0.2.0",
"@floating-ui/dom": "^1.6.7",
"@sveltejs/adapter-node": "^5.2.0",
"fluent-ffmpeg": "^2.1.3",
Expand Down
4 changes: 3 additions & 1 deletion src/app.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
// for information about these interfaces
// and what to do when importing types
declare namespace App {
// interface Locals {}
interface Locals {
user: string;
}
// interface PageData {}
// interface Error {}
// interface Platform {}
Expand Down
8 changes: 6 additions & 2 deletions src/hooks.server.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { handle } from '../src/hooks.server';
import { describe, it, expect, vi } from 'vitest';

function createEvent() {

Check warning on line 4 in src/hooks.server.test.ts

View workflow job for this annotation

GitHub Actions / lint

'createEvent' is defined but never used. Allowed unused vars must match /^_/u
return {
request: {
headers: new Map(),
Expand All @@ -24,13 +24,17 @@

describe('handle requests', () => {
it('should set request headers and log request information', async () => {
const event = createEvent();
const event = {
locals: {},
request: new Request('https://example.com'),
resolve: async () => new Response()
};

const resolve = createResolve();

const response = await handle({ event, resolve });

expect(event.request.headers.get('X-Request-ID')).toBeDefined();
expect(event.request.headers.get('X-User')).toBe('[email protected]');
expect(resolve).toHaveBeenCalledWith(event);
expect(response.headers.append).toHaveBeenCalledWith('X-Request-ID', expect.any(String));
});
Expand Down
7 changes: 3 additions & 4 deletions src/hooks.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,16 @@ export async function handle({ event, resolve }) {
const requestId = randomUUID();
const startTime = performance.now();

const user =
event.locals.user =
event.request.headers.get('X-MS-CLIENT-PRINCIPAL-NAME') || '[email protected]';
logger.info({
type: `Request`,
method: event.request.method,
url: event.request.url,
user,
user: event.locals.user,
requestId
});
event.request.headers.set('X-Request-ID', requestId);
event.request.headers.set('X-User', user);

const response = await resolve(event);

Expand All @@ -27,7 +26,7 @@ export async function handle({ event, resolve }) {
statusCode: response.status,
method: event.request.method,
url: event.request.url,
user,
user: event.locals.user,
requestId,
responseTime
};
Expand Down
2 changes: 2 additions & 0 deletions src/lib/components/Navigation.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
const pages = [
{ name: 'Chatbot', href: '/gpt' },
{ name: 'Document Intelligence', href: '/document-intelligence' },
{ name: 'Diarize (Submit)', href: '/diarize-submit' },
{ name: 'Diarize (Retrieve)', href: '/diarize-retrieve' },
{ name: 'Embedding', href: '/embedding' },
{ name: 'Image Generation', href: '/dalle' },
{ name: 'Text To Speech', href: '/text-to-speech' },
Expand Down
4 changes: 2 additions & 2 deletions src/lib/components/PageTemplates/FormBasePage.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
import { page } from '$app/stores';
import { getToastStore, type ToastSettings } from '@skeletonlabs/skeleton';
import LoadingBar from '$lib/components/LoadingBar.svelte';
import NoBaaBanner from '$lib/components/BAA/NoBaaBanner.svelte';
import HasBaaBanner from '$lib/components/BAA/HasBaaBanner.svelte';
import NoBaaBanner from '$lib/components/banners/NoBaaBanner.svelte';
import HasBaaBanner from '$lib/components/banners/HasBaaBanner.svelte';

export let title: string;
export let description: string;
Expand Down
9 changes: 9 additions & 0 deletions src/lib/components/banners/BetaBanner.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<script lang="ts">
import BaseBanner from './BaseBanner.svelte';
</script>

<BaseBanner
title="Beta Feature"
message="This feature is currently in beta. If you encounter any issues, please contact [email protected]."
variant="warning"
/>
1 change: 1 addition & 0 deletions src/lib/icons/DownloadIcon.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<i class={`fas fa-download ${$$props.class}`} />
5 changes: 5 additions & 0 deletions src/lib/server/secrets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ export const AZURE_OPENAI_GPT_DEPLOYMENT_NAME = env.AZURE_OPENAI_GPT_DEPLOYMENT_
export const AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME =
env.AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME || '';
export const AZURE_OPENAI_WHISPER_DEPLOYMENT_NAME = env.AZURE_OPENAI_WHISPER_DEPLOYMENT_NAME || '';

export const AZURE_SPEECH_SERVICES_ENDPOINT = env.AZURE_SPEECH_SERVICES_ENDPOINT || '';
export const AZURE_SPEECH_SERVICES_KEY = env.AZURE_SPEECH_SERVICES_KEY || '';
export const AZURE_CREATE_DIARIZATION_ENDPOINT = env.AZURE_CREATE_DIARIZATION_ENDPOINT || '';

export const AZURE_DOCUMENT_INTELLIGENCE_ENDPOINT = env.AZURE_DOCUMENT_INTELLIGENCE_ENDPOINT || '';
export const AZURE_DOCUMENT_INTELLIGENCE_KEY = env.AZURE_DOCUMENT_INTELLIGENCE_KEY || '';

Expand Down
33 changes: 33 additions & 0 deletions src/lib/server/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import winston from 'winston';
import ffmpeg from 'fluent-ffmpeg';
import { diskFileToMemoryFile, memoryFileToDiskFile } from '$lib/fileHandling';
import fs from 'fs';

const { combine, timestamp, json } = winston.format;
export const logger = winston.createLogger({
Expand All @@ -11,3 +14,33 @@ export const logger = winston.createLogger({
),
transports: [new winston.transports.Console()]
});

export async function convertToMp3(file: File): Promise<File> {
const fileExtension = file.name.split('.').pop();
if (fileExtension == 'mp3') return file;

const timestamp = new Date().getTime();

const inputName = `tempInputFile_${timestamp}.${fileExtension}`;
const convertedFileName = `temp_${timestamp}.mp3`;

try {
await memoryFileToDiskFile(file, inputName);
await new Promise<void>((resolve, reject) => {
ffmpeg(inputName)
.setStartTime(0)
.output(convertedFileName)
.on('error', (err) => {
logger.error(err);
reject();
})
.on('end', () => resolve())
.run();
});
const outputFile = diskFileToMemoryFile(convertedFileName, 'audio/mp3');
return outputFile;
} finally {
fs.unlinkSync(inputName);
fs.unlinkSync(convertedFileName);
}
}
48 changes: 48 additions & 0 deletions src/routes/api/diarize-retrieve/+server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { AZURE_SPEECH_SERVICES_KEY } from '$lib/server/secrets';

type TranscriptionLocationResponse = {
values: {
self: string;
name: string;
kind: 'Transcription' | 'TranscriptionReport';
properties: {
size: number;
};
createdDateTime: string;
links: {
contentUrl: string;
};
}[];
};

export async function GET({ request }) {
const url = request.headers.get('X-Transcript-Url');
if (url === null) {
return new Response('No valid url.', { status: 400 });
}

const transcriptLocationJson: TranscriptionLocationResponse = await (
await fetch(url, {
headers: {
'Ocp-Apim-Subscription-Key': AZURE_SPEECH_SERVICES_KEY
}
})
).json();

const urls = transcriptLocationJson.values
.filter((json) => json.kind === 'Transcription')
.map((json) => {
return { name: json.name, link: json.links.contentUrl };
});
const responses = await Promise.all(
urls.map(async (url) => {
return await fetch(url.link, {
headers: {
'Ocp-Apim-Subscription-Key': AZURE_SPEECH_SERVICES_KEY
}
});
})
);

return new Response(responses[0].body);
}
5 changes: 2 additions & 3 deletions src/routes/api/gpt/+server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { Message } from '$lib/types';
import { logger } from '$lib/server/utils';
import { AZURE_OPENAI_GPT_DEPLOYMENT_NAME } from '$lib/server/secrets';

export async function POST({ request }) {
export async function POST({ request, locals }) {
const data = await request.json();
const messages = data.messages as Message[];
const azureOpenai = getAzureOpenAiClient();
Expand All @@ -16,12 +16,11 @@ export async function POST({ request }) {
}

const requestId = request.headers.get('X-Request-ID');
const user = request.headers.get('X-User');
logger.info({
type: 'OpenAI Request',
AZURE_OPENAI_GPT_DEPLOYMENT_NAME,
requestId,
user
user: locals.user
});

const completion = await azureOpenai.streamChatCompletions(
Expand Down
5 changes: 2 additions & 3 deletions src/routes/api/text-to-speech/+server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import OpenAI from 'openai';
import { logger } from '$lib/server/utils';
import { OPENAI_API_KEY } from '$lib/server/secrets';

export async function POST({ request }) {
export async function POST({ request, locals }) {
const openai = new OpenAI({ apiKey: OPENAI_API_KEY });

const formData = await request.formData();
Expand All @@ -16,12 +16,11 @@ export async function POST({ request }) {
}

const requestId = request.headers.get('X-Request-ID');
const user = request.headers.get('X-User');
logger.info({
type: 'OpenAI Request',
model,
requestId,
user
user: locals.user
});
const audioResponse = await openai.audio.speech.create({
input,
Expand Down
3 changes: 1 addition & 2 deletions src/routes/dalle/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,11 @@ export const actions = {

const model = 'dall-e-3';
const requestId = event.request.headers.get('X-Request-ID');
const user = event.request.headers.get('X-User');
logger.info({
type: 'OpenAI Request',
model,
requestId,
user
user: event.locals.user
});
const responses: APIPromise<ImagesResponse>[] = [];
for (let i = 0; i < number; i++) {
Expand Down
5 changes: 1 addition & 4 deletions src/routes/dalle/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,7 @@

const maxImages = 4;
const title = 'Image Generation';
const description = `
This tool lets you create images with DALL-E 3. Simply write what you want to see and select the
desired size and number of images.
`;
const description = `This tool lets you create images with DALL-E 3. Simply write what you want to see and select the desired size and number of images.`;
const enhancer: SubmitFunction = () => {
return async ({ update }) => {
await update({ reset: false });
Expand Down
24 changes: 24 additions & 0 deletions src/routes/diarize-retrieve/+page.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { AZURE_SPEECH_SERVICES_ENDPOINT, AZURE_SPEECH_SERVICES_KEY } from '$lib/server/secrets';
import type { TranscriptionResponse } from './types';

export async function load({ fetch, locals }) {
const transcriptions: TranscriptionResponse = await fetch(
AZURE_SPEECH_SERVICES_ENDPOINT + '/speechtotext/v3.2/transcriptions',
{
headers: {
'Ocp-Apim-Subscription-Key': AZURE_SPEECH_SERVICES_KEY
}
}
).then(async (resp) => await resp.json());
const userTranscriptions = transcriptions.values
.filter((transcript) => {
// As a bit of a user-management hack, the owner is stored in the display name.
return transcript.displayName.split('-!-')[0] === locals.user;
})
.map((transcript) => {
transcript.displayName = transcript.displayName.split('-!-')[1] ?? transcript.displayName;
return transcript;
})
.sort((a, b) => b.createdDateTime.localeCompare(a.createdDateTime));
return { values: userTranscriptions };
}
62 changes: 62 additions & 0 deletions src/routes/diarize-retrieve/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<script lang="ts">
import BetaBanner from '$lib/components/banners/BetaBanner.svelte';
import HasBaaBanner from '$lib/components/banners/HasBaaBanner.svelte';
import { downloadBlob } from '$lib/fileHandling';
import DownloadIcon from '$lib/icons/DownloadIcon.svelte';
import type { Transcription, TranscriptionResponse } from './types';

export let data: TranscriptionResponse;
async function downloadFile(transcription: Transcription) {
const response = await fetch('/api/diarize-retrieve', {
headers: {
'X-Transcript-Url': transcription.links.files
}
});
const blob = await response.blob();
const fileparts = transcription.displayName.split('.');
fileparts.pop();
const filename = fileparts.join('') + '.json';
downloadBlob(blob, filename);
}
</script>

<div class="space-y-2">
<BetaBanner />
<HasBaaBanner />
</div>
<span>
Here you may find the results of your submitted diarizations. Please note that it can take up to a
few hours before your diarizations are completed. Diarization results are deleted after a week.
</span>

<div class="table-container">
<table class="table table-hover">
<thead>
<tr>
<th>Name</th>
<th>Created at (UTC)</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{#each data.values as transcription}
<tr>
<td>{transcription.displayName}</td>
<td>{transcription.createdDateTime}</td>
<td>
{#if transcription.status === 'Succeeded'}
<button
class="btn btn-sm hover:variant-ghost-primary"
on:click={() => downloadFile(transcription)}
>
<DownloadIcon class="text-lg" />
</button>
{:else}
{transcription.status}
{/if}
</td>
</tr>
{/each}
</tbody>
</table>
</div>
Loading