diff --git a/samples/chatbot/genkit-app/src/app/app.component.scss b/samples/chatbot/genkit-app/src/app/app.component.scss
new file mode 100644
index 000000000..0e0b80557
--- /dev/null
+++ b/samples/chatbot/genkit-app/src/app/app.component.scss
@@ -0,0 +1,67 @@
+/**
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+app-bar {
+ border-bottom: 1px solid var(--divider-color);
+ grid-area: header;
+}
+
+article {
+ grid-area: content;
+}
+
+.wrapper {
+ display: grid;
+ grid-template:
+ 'header' auto
+ 'content' 1fr
+ / 1fr;
+ height: 100vh;
+}
+
+.home-link {
+ align-items: center;
+ color: var(--mat-app-color);
+ display: flex;
+ gap: 8px;
+
+ img {
+ height: 22px;
+ padding-left: 4px;
+ }
+}
+
+.mat-toolbar {
+ background: #d7e3ff;
+ color: #005cbb;
+ gap: 4px;
+}
+
+nav {
+ --mdc-secondary-navigation-tab-container-height: 64px;
+ --mat-tab-header-divider-height: 0;
+ margin-left: 32px;
+}
+
+.preview-badge {
+ margin-left: 8px;
+
+ mat-icon {
+ font-size: 18px;
+ height: 18px;
+ width: 18px;
+ }
+}
diff --git a/samples/chatbot/genkit-app/src/app/app.component.spec.ts b/samples/chatbot/genkit-app/src/app/app.component.spec.ts
new file mode 100644
index 000000000..02dac7e71
--- /dev/null
+++ b/samples/chatbot/genkit-app/src/app/app.component.spec.ts
@@ -0,0 +1,47 @@
+/**
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { TestBed } from '@angular/core/testing';
+import { AppComponent } from './app.component';
+
+describe('AppComponent', () => {
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [AppComponent],
+ }).compileComponents();
+ });
+
+ it('should create the app', () => {
+ const fixture = TestBed.createComponent(AppComponent);
+ const app = fixture.componentInstance;
+ expect(app).toBeTruthy();
+ });
+
+ it(`should have the 'genkit-app' title`, () => {
+ const fixture = TestBed.createComponent(AppComponent);
+ const app = fixture.componentInstance;
+ expect(app.title).toEqual('genkit-app');
+ });
+
+ it('should render title', () => {
+ const fixture = TestBed.createComponent(AppComponent);
+ fixture.detectChanges();
+ const compiled = fixture.nativeElement as HTMLElement;
+ expect(compiled.querySelector('h1')?.textContent).toContain(
+ 'Hello, genkit-app'
+ );
+ });
+});
diff --git a/samples/chatbot/genkit-app/src/app/app.component.ts b/samples/chatbot/genkit-app/src/app/app.component.ts
new file mode 100644
index 000000000..39d4f4ea0
--- /dev/null
+++ b/samples/chatbot/genkit-app/src/app/app.component.ts
@@ -0,0 +1,45 @@
+/**
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { CommonModule } from '@angular/common';
+import { Component } from '@angular/core';
+import { MatButtonModule } from '@angular/material/button';
+import { MatIconModule } from '@angular/material/icon';
+import { MatTabNavPanel, MatTabsModule } from '@angular/material/tabs';
+import { MatToolbarModule } from '@angular/material/toolbar';
+import { MatTooltipModule } from '@angular/material/tooltip';
+import { RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
+
+@Component({
+ selector: 'app-root',
+ standalone: true,
+ imports: [
+ CommonModule,
+ MatToolbarModule,
+ RouterOutlet,
+ MatIconModule,
+ MatTabNavPanel,
+ MatButtonModule,
+ MatTabsModule,
+ MatToolbarModule,
+ MatTooltipModule,
+ RouterLink,
+ RouterLinkActive,
+ ],
+ templateUrl: './app.component.html',
+ styleUrl: './app.component.scss',
+})
+export class AppComponent {}
diff --git a/samples/chatbot/genkit-app/src/app/app.config.ts b/samples/chatbot/genkit-app/src/app/app.config.ts
new file mode 100644
index 000000000..7c47634a3
--- /dev/null
+++ b/samples/chatbot/genkit-app/src/app/app.config.ts
@@ -0,0 +1,31 @@
+/**
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
+import { provideRouter } from '@angular/router';
+
+import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
+import { provideMarkdown } from 'ngx-markdown';
+import { routes } from './app.routes';
+
+export const appConfig: ApplicationConfig = {
+ providers: [
+ provideZoneChangeDetection({ eventCoalescing: true }),
+ provideRouter(routes),
+ provideAnimationsAsync(),
+ provideMarkdown(),
+ ],
+};
diff --git a/samples/chatbot/genkit-app/src/app/app.routes.ts b/samples/chatbot/genkit-app/src/app/app.routes.ts
new file mode 100644
index 000000000..d59a096c2
--- /dev/null
+++ b/samples/chatbot/genkit-app/src/app/app.routes.ts
@@ -0,0 +1,26 @@
+/**
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Routes } from '@angular/router';
+import { ChatbotComponent } from './samples/chatbot/chatbot.component';
+
+export const routes: Routes = [
+ {
+ path: 'home',
+ component: ChatbotComponent,
+ },
+ { path: '**', redirectTo: '/home' },
+];
diff --git a/samples/chatbot/genkit-app/src/app/samples/chatbot/chatbot.component.html b/samples/chatbot/genkit-app/src/app/samples/chatbot/chatbot.component.html
new file mode 100644
index 000000000..782825689
--- /dev/null
+++ b/samples/chatbot/genkit-app/src/app/samples/chatbot/chatbot.component.html
@@ -0,0 +1,58 @@
+
+
+
+ @if (llmIndex === undefined) {
+
Choose an LLM
+ @for (name of llmNames; track name; let index = $index) {
+
+ }
+ } @else {
+
Chat with {{ llmNames[llmIndex] }}
+
+
+
+ {{ entry.text }}
+
+
+
+
+
+
+
+
+ @if (error) {
+
{{ error }}
+ }
+
+
+ }
+
diff --git a/samples/chatbot/genkit-app/src/app/samples/chatbot/chatbot.component.scss b/samples/chatbot/genkit-app/src/app/samples/chatbot/chatbot.component.scss
new file mode 100644
index 000000000..02802405d
--- /dev/null
+++ b/samples/chatbot/genkit-app/src/app/samples/chatbot/chatbot.component.scss
@@ -0,0 +1,75 @@
+/**
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+.wrapper {
+ margin-left: auto;
+ margin-right: auto;
+ padding: 20px;
+ width: 800px;
+}
+
+.user-bubble {
+ background-color: bisque;
+ border: 1px solid #ccc;
+ border-radius: 10px;
+ margin-bottom: 20px;
+ margin-left: auto;
+ margin-right: 0;
+ min-width: 300px;
+ padding: 20px;
+ white-space: pre-wrap;
+ width: 80%;
+}
+
+.model-bubble {
+ border: 1px solid #ccc;
+ border-radius: 10px;
+ margin-bottom: 20px;
+ min-width: 300px;
+ padding: 20px;
+ width: 80%;
+ &.llm-0 {
+ background-color: rgb(218, 247, 237);
+ }
+ &.llm-1 {
+ background-color: rgb(244, 216, 247);
+ }
+
+ .text {
+ white-space: pre-wrap;
+ }
+
+ .model-name {
+ font-size: small;
+ font-weight: 100;
+ margin-bottom: 11px;
+ }
+}
+
+.input-field {
+ min-width: 400px;
+ vertical-align: top;
+ width: 730px;
+}
+
+.error {
+ background-color: pink;
+ border: 1px solid red;
+ border-radius: 15px;
+ margin: 20px 0;
+ overflow: auto;
+ padding: 20px;
+}
diff --git a/samples/chatbot/genkit-app/src/app/samples/chatbot/chatbot.component.spec.ts b/samples/chatbot/genkit-app/src/app/samples/chatbot/chatbot.component.spec.ts
new file mode 100644
index 000000000..c79a6e1f7
--- /dev/null
+++ b/samples/chatbot/genkit-app/src/app/samples/chatbot/chatbot.component.spec.ts
@@ -0,0 +1,38 @@
+/**
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ChatbotComponent } from './chatbot.component';
+
+describe('ChatbotComponent', () => {
+ let component: ChatbotComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [ChatbotComponent],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(ChatbotComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/samples/chatbot/genkit-app/src/app/samples/chatbot/chatbot.component.ts b/samples/chatbot/genkit-app/src/app/samples/chatbot/chatbot.component.ts
new file mode 100644
index 000000000..293bdf106
--- /dev/null
+++ b/samples/chatbot/genkit-app/src/app/samples/chatbot/chatbot.component.ts
@@ -0,0 +1,157 @@
+/**
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { CommonModule } from '@angular/common';
+import { Component } from '@angular/core';
+import {
+ FormControl,
+ FormsModule,
+ ReactiveFormsModule,
+ Validators,
+} from '@angular/forms';
+import { MatButtonModule } from '@angular/material/button';
+import { provideNativeDateAdapter } from '@angular/material/core';
+import { MatDatepickerModule } from '@angular/material/datepicker';
+import { MatFormFieldModule } from '@angular/material/form-field';
+import { MatIconModule } from '@angular/material/icon';
+import { MatInputModule } from '@angular/material/input';
+import { MatProgressBarModule } from '@angular/material/progress-bar';
+import { MatRadioModule } from '@angular/material/radio';
+import { streamFlow } from 'genkit/client';
+import { MarkdownModule } from 'ngx-markdown';
+
+const url = 'http://127.0.0.1:3400/chatbotFlow';
+
+interface ToolResponse {
+ name: string;
+ ref: string;
+ output?: unknown;
+}
+
+interface InputSchema {
+ role: 'user';
+ text?: string;
+ toolResponse?: ToolResponse;
+}
+
+interface ToolRequest {
+ name: string;
+ ref: string;
+ input?: unknown;
+}
+interface OutputSchema {
+ role: 'model';
+ text?: string;
+ toolRequest?: ToolRequest;
+}
+
+@Component({
+ selector: 'app-chatbot',
+ standalone: true,
+ providers: [provideNativeDateAdapter()],
+ imports: [
+ CommonModule,
+ FormsModule,
+ MatFormFieldModule,
+ MatInputModule,
+ ReactiveFormsModule,
+ MatButtonModule,
+ MatIconModule,
+ MatProgressBarModule,
+ MatDatepickerModule,
+ MarkdownModule,
+ MatRadioModule,
+ ],
+ templateUrl: './chatbot.component.html',
+ styleUrl: './chatbot.component.scss',
+})
+export class ChatbotComponent {
+ history: (InputSchema | OutputSchema)[] = [];
+ error?: string;
+ input?: string;
+ loading = false;
+ id = Date.now() + '' + Math.floor(Math.random() * 1000000000);
+ llmIndex: number | undefined;
+
+ llmNames = ['Gemini 1.5 Flash', 'Llama 3.1 405b'];
+
+ chatFormControl = new FormControl(
+ 'write a function that adds two number together',
+ [Validators.required]
+ );
+
+ ask(input?: string) {
+ const text = this.chatFormControl.value!.trim();
+ if (!text) return;
+ this.history.push({ role: 'user', text: text });
+ this.chatFormControl.setValue('');
+ this.chatFormControl.disable();
+ this.callFlow({ role: 'user', text });
+ this.loading = true;
+ }
+
+ async callFlow(input: InputSchema) {
+ this.error = undefined;
+ this.loading = true;
+ try {
+ const response = await streamFlow({
+ url,
+ input: {
+ prompt: input,
+ conversationId: this.id,
+ llmIndex: this.llmIndex,
+ },
+ });
+
+ let textBlock: OutputSchema | undefined = undefined;
+ for await (const chunk of response.stream()) {
+ for (const content of chunk.content) {
+ if (content.text) {
+ if (!textBlock) {
+ textBlock = { role: 'model', text: content.text! };
+ this.history.push(textBlock);
+ } else {
+ textBlock.text += content.text!;
+ }
+ }
+ }
+ }
+
+ this.loading = false;
+ this.chatFormControl.enable();
+
+ await response.output();
+ } catch (e) {
+ this.loading = false;
+ this.chatFormControl.enable();
+ if ((e as any).cause) {
+ this.error = `${(e as any).cause}`;
+ } else {
+ this.error = `${e}`;
+ }
+ }
+ }
+
+ keyPress(event: KeyboardEvent) {
+ if (event.key === 'Enter') {
+ if (event.ctrlKey || event.shiftKey) {
+ this.input += '\n';
+ } else {
+ this.ask(this.input);
+ }
+ }
+ }
+}
diff --git a/samples/chatbot/genkit-app/src/index.html b/samples/chatbot/genkit-app/src/index.html
new file mode 100644
index 000000000..822a28173
--- /dev/null
+++ b/samples/chatbot/genkit-app/src/index.html
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+ GenkitApp
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/chatbot/genkit-app/src/main.ts b/samples/chatbot/genkit-app/src/main.ts
new file mode 100644
index 000000000..b1be530a2
--- /dev/null
+++ b/samples/chatbot/genkit-app/src/main.ts
@@ -0,0 +1,23 @@
+/**
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { bootstrapApplication } from '@angular/platform-browser';
+import { AppComponent } from './app/app.component';
+import { appConfig } from './app/app.config';
+
+bootstrapApplication(AppComponent, appConfig).catch((err) =>
+ console.error(err)
+);
diff --git a/samples/chatbot/genkit-app/src/styles.scss b/samples/chatbot/genkit-app/src/styles.scss
new file mode 100644
index 000000000..0ff0b59e1
--- /dev/null
+++ b/samples/chatbot/genkit-app/src/styles.scss
@@ -0,0 +1,55 @@
+/**
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* You can add global styles to this file, and also import other style files */
+
+:root {
+ --header-height: 65px;
+ --container-border-radius: 20px;
+ --input-border-radius: 8px;
+}
+
+html,
+body {
+ height: 100%;
+}
+
+body {
+ background-color: var(--app-background);
+ color: var(--mat-app-text-color);
+ margin: 0;
+}
+
+hr {
+ border-bottom: 1px solid var(--divider-color);
+ border-width: 0 0 1px;
+ margin: 12px 0;
+}
+
+a {
+ color: var(--link-color);
+ text-decoration: none;
+}
+
+pre {
+ margin: 0;
+ white-space: pre-wrap;
+}
+
+// Helper for filling available space in flex layouts
+.flex-spacer {
+ flex: 1;
+}
diff --git a/samples/chatbot/genkit-app/tsconfig.app.json b/samples/chatbot/genkit-app/tsconfig.app.json
new file mode 100644
index 000000000..84f1f992d
--- /dev/null
+++ b/samples/chatbot/genkit-app/tsconfig.app.json
@@ -0,0 +1,10 @@
+/* To learn more about this file see: https://angular.io/config/tsconfig. */
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "outDir": "./out-tsc/app",
+ "types": []
+ },
+ "files": ["src/main.ts"],
+ "include": ["src/**/*.d.ts"]
+}
diff --git a/samples/chatbot/genkit-app/tsconfig.json b/samples/chatbot/genkit-app/tsconfig.json
new file mode 100644
index 000000000..437984834
--- /dev/null
+++ b/samples/chatbot/genkit-app/tsconfig.json
@@ -0,0 +1,29 @@
+/* To learn more about this file see: https://angular.io/config/tsconfig. */
+{
+ "compileOnSave": false,
+ "compilerOptions": {
+ "outDir": "./dist/out-tsc",
+ "strict": true,
+ "noImplicitOverride": true,
+ "noPropertyAccessFromIndexSignature": true,
+ "noImplicitReturns": true,
+ "noFallthroughCasesInSwitch": true,
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "sourceMap": true,
+ "declaration": false,
+ "experimentalDecorators": true,
+ "moduleResolution": "bundler",
+ "importHelpers": true,
+ "target": "ES2022",
+ "module": "ES2022",
+ "useDefineForClassFields": false,
+ "lib": ["ES2022", "dom"]
+ },
+ "angularCompilerOptions": {
+ "enableI18nLegacyMessageIdFormat": false,
+ "strictInjectionParameters": true,
+ "strictInputAccessModifiers": true,
+ "strictTemplates": true
+ }
+}
diff --git a/samples/chatbot/genkit-app/tsconfig.spec.json b/samples/chatbot/genkit-app/tsconfig.spec.json
new file mode 100644
index 000000000..47e3dd755
--- /dev/null
+++ b/samples/chatbot/genkit-app/tsconfig.spec.json
@@ -0,0 +1,9 @@
+/* To learn more about this file see: https://angular.io/config/tsconfig. */
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "outDir": "./out-tsc/spec",
+ "types": ["jasmine"]
+ },
+ "include": ["src/**/*.spec.ts", "src/**/*.d.ts"]
+}
diff --git a/samples/chatbot/package.json b/samples/chatbot/package.json
new file mode 100644
index 000000000..5cfb52ff9
--- /dev/null
+++ b/samples/chatbot/package.json
@@ -0,0 +1,17 @@
+{
+ "scripts": {
+ "start": "concurrently npm:start:server npm:start:ng",
+ "setup": "npm i && cd server && npm i && cd ../genkit-app && npm i",
+ "start:server": "cd server && npm run genkit:dev",
+ "start:ng": "cd genkit-app && npm start"
+ },
+ "name": "js-angular",
+ "version": "1.0.0",
+ "description": "This is a simple UI for streaming RPG character generator.",
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "devDependencies": {
+ "concurrently": "^8.2.2"
+ }
+}
diff --git a/samples/chatbot/server/package.json b/samples/chatbot/server/package.json
new file mode 100644
index 000000000..4f13d4997
--- /dev/null
+++ b/samples/chatbot/server/package.json
@@ -0,0 +1,29 @@
+{
+ "main": "lib/index.js",
+ "scripts": {
+ "start": "node lib/index.js",
+ "genkit:dev": "genkit start -- npm run dev",
+ "dev": "tsx --watch src/index.ts",
+ "build": "tsc",
+ "build:watch": "tsc --watch",
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "name": "js-angular",
+ "version": "1.0.0",
+ "description": "This is a simple UI for streaming RPG character generator.",
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "genkit": "^0.9.0-rc || ^0.9",
+ "@genkit-ai/vertexai": "^0.9.0-rc || ^0.9",
+ "express": "^4.21.0",
+ "partial-json": "^0.1.7",
+ "zod": "^3.23.8"
+ },
+ "devDependencies": {
+ "genkit-cli": "^0.9.0-rc || ^0.9",
+ "typescript": "^5.4.5",
+ "tsx": "^4.19.2"
+ }
+}
diff --git a/samples/chatbot/server/src/index.ts b/samples/chatbot/server/src/index.ts
new file mode 100644
index 000000000..7bd2144bf
--- /dev/null
+++ b/samples/chatbot/server/src/index.ts
@@ -0,0 +1,100 @@
+/**
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { gemini15Flash, vertexAI } from '@genkit-ai/vertexai';
+import {
+ VertexAIEvaluationMetricType,
+ vertexAIEvaluation,
+} from '@genkit-ai/vertexai/evaluation';
+import { llama31, vertexAIModelGarden } from '@genkit-ai/vertexai/modelgarden';
+import { ModelReference, PartSchema, genkit, run } from 'genkit';
+import { GenerateResponseChunkSchema } from 'genkit/model';
+import { z } from 'zod';
+import { inMemoryStore } from './memory.js';
+
+export const AgentInput = z.object({
+ conversationId: z.string(),
+ prompt: z.union([z.string(), PartSchema, z.array(PartSchema)]),
+ config: z.record(z.string(), z.any()).optional(),
+ llmIndex: z.number(),
+});
+
+const ai = genkit({
+ plugins: [
+ vertexAI({
+ location: 'us-central1',
+ }),
+ vertexAIModelGarden({
+ location: 'us-central1',
+ models: [llama31],
+ }),
+ vertexAIEvaluation({
+ location: 'us-central1',
+ metrics: [
+ VertexAIEvaluationMetricType.SAFETY,
+ VertexAIEvaluationMetricType.FLUENCY,
+ ],
+ }),
+ ],
+});
+
+const llms: ModelReference[] = [gemini15Flash, llama31];
+
+const historyStore = inMemoryStore();
+
+export const chatbotFlow = ai.defineStreamingFlow(
+ {
+ name: 'chatbotFlow',
+ inputSchema: AgentInput,
+ outputSchema: z.string(),
+ streamSchema: GenerateResponseChunkSchema,
+ },
+ async (request, streamingCallback) => {
+ // Retrieve conversation history.
+ const history = await run(
+ 'retrieve-history',
+ request.conversationId,
+ async () => {
+ return (await historyStore?.load(request.conversationId)) || [];
+ }
+ );
+
+ // Run the user prompt (with history) through the primary LLM.
+ const mainResp = await ai.generate({
+ prompt: request.prompt,
+ messages: history,
+ model: llms[request.llmIndex],
+ streamingCallback,
+ });
+
+ // Save history.
+ await run(
+ 'save-history',
+ {
+ conversationId: request.conversationId,
+ history: mainResp.messages,
+ },
+ async () => {
+ await historyStore?.save(request.conversationId, mainResp.messages);
+ }
+ );
+ return mainResp.text;
+ }
+);
+
+ai.startFlowServer({
+ flows: [chatbotFlow],
+});
diff --git a/samples/chatbot/server/src/memory.ts b/samples/chatbot/server/src/memory.ts
new file mode 100644
index 000000000..4c3561f01
--- /dev/null
+++ b/samples/chatbot/server/src/memory.ts
@@ -0,0 +1,35 @@
+/**
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { MessageData } from 'genkit';
+
+const chatHistory: Record = {};
+
+export interface HistoryStore {
+ load(id: string): Promise;
+ save(id: string, history: MessageData[]): Promise;
+}
+
+export function inMemoryStore(): HistoryStore {
+ return {
+ async load(id: string): Promise {
+ return chatHistory[id];
+ },
+ async save(id: string, history: MessageData[]) {
+ chatHistory[id] = history;
+ },
+ };
+}
diff --git a/samples/chatbot/server/tsconfig.json b/samples/chatbot/server/tsconfig.json
new file mode 100644
index 000000000..efbb566bf
--- /dev/null
+++ b/samples/chatbot/server/tsconfig.json
@@ -0,0 +1,14 @@
+{
+ "compileOnSave": true,
+ "include": ["src"],
+ "compilerOptions": {
+ "module": "commonjs",
+ "noImplicitReturns": true,
+ "outDir": "lib",
+ "sourceMap": true,
+ "strict": true,
+ "target": "es2017",
+ "skipLibCheck": true,
+ "esModuleInterop": true
+ }
+}
diff --git a/samples/js-angular/.gitignore b/samples/js-angular/.gitignore
new file mode 100644
index 000000000..7951405f8
--- /dev/null
+++ b/samples/js-angular/.gitignore
@@ -0,0 +1 @@
+lib
\ No newline at end of file
diff --git a/samples/js-angular/README.md b/samples/js-angular/README.md
new file mode 100644
index 000000000..4e02b6f32
--- /dev/null
+++ b/samples/js-angular/README.md
@@ -0,0 +1,24 @@
+# Angular and Genkit streaming sample
+
+This is a simple UI for streaming RPG character generator.
+
+To build:
+
+```bash
+npm i
+npm run build
+```
+
+The sample is using Vertex AI, so you'll need to auth:
+
+```bash
+gcloud auth application-default login
+```
+
+To run the sample:
+
+```bash
+npm start
+```
+
+Point your browser to http://localhost:4200/
diff --git a/samples/js-angular/genkit-app/.editorconfig b/samples/js-angular/genkit-app/.editorconfig
new file mode 100644
index 000000000..59d9a3a3e
--- /dev/null
+++ b/samples/js-angular/genkit-app/.editorconfig
@@ -0,0 +1,16 @@
+# Editor configuration, see https://editorconfig.org
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 2
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.ts]
+quote_type = single
+
+[*.md]
+max_line_length = off
+trim_trailing_whitespace = false
diff --git a/samples/js-angular/genkit-app/.gitignore b/samples/js-angular/genkit-app/.gitignore
new file mode 100644
index 000000000..cc7b14135
--- /dev/null
+++ b/samples/js-angular/genkit-app/.gitignore
@@ -0,0 +1,42 @@
+# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
+
+# Compiled output
+/dist
+/tmp
+/out-tsc
+/bazel-out
+
+# Node
+/node_modules
+npm-debug.log
+yarn-error.log
+
+# IDEs and editors
+.idea/
+.project
+.classpath
+.c9/
+*.launch
+.settings/
+*.sublime-workspace
+
+# Visual Studio Code
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+.history/*
+
+# Miscellaneous
+/.angular/cache
+.sass-cache/
+/connect.lock
+/coverage
+/libpeerconnection.log
+testem.log
+/typings
+
+# System files
+.DS_Store
+Thumbs.db
diff --git a/samples/js-angular/genkit-app/README.md b/samples/js-angular/genkit-app/README.md
new file mode 100644
index 000000000..0aeb2095b
--- /dev/null
+++ b/samples/js-angular/genkit-app/README.md
@@ -0,0 +1,27 @@
+# GenkitApp
+
+This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 18.0.2.
+
+## Development server
+
+Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.
+
+## Code scaffolding
+
+Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
+
+## Build
+
+Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
+
+## Running unit tests
+
+Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
+
+## Running end-to-end tests
+
+Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
+
+## Further help
+
+To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page.
diff --git a/samples/js-angular/genkit-app/angular.json b/samples/js-angular/genkit-app/angular.json
new file mode 100644
index 000000000..8762ae48e
--- /dev/null
+++ b/samples/js-angular/genkit-app/angular.json
@@ -0,0 +1,99 @@
+{
+ "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
+ "version": 1,
+ "newProjectRoot": "projects",
+ "projects": {
+ "genkit-app": {
+ "projectType": "application",
+ "schematics": {
+ "@schematics/angular:component": {
+ "style": "scss"
+ }
+ },
+ "root": "",
+ "sourceRoot": "src",
+ "prefix": "app",
+ "architect": {
+ "build": {
+ "builder": "@angular-devkit/build-angular:application",
+ "options": {
+ "outputPath": "dist/genkit-app",
+ "index": "src/index.html",
+ "browser": "src/main.ts",
+ "polyfills": ["zone.js"],
+ "tsConfig": "tsconfig.app.json",
+ "inlineStyleLanguage": "scss",
+ "assets": [
+ {
+ "glob": "**/*",
+ "input": "public"
+ }
+ ],
+ "styles": [
+ "@angular/material/prebuilt-themes/azure-blue.css",
+ "src/styles.scss"
+ ],
+ "scripts": []
+ },
+ "configurations": {
+ "production": {
+ "budgets": [
+ {
+ "type": "initial",
+ "maximumWarning": "500kB",
+ "maximumError": "1MB"
+ },
+ {
+ "type": "anyComponentStyle",
+ "maximumWarning": "2kB",
+ "maximumError": "4kB"
+ }
+ ],
+ "outputHashing": "all"
+ },
+ "development": {
+ "optimization": false,
+ "extractLicenses": false,
+ "sourceMap": true
+ }
+ },
+ "defaultConfiguration": "production"
+ },
+ "serve": {
+ "builder": "@angular-devkit/build-angular:dev-server",
+ "configurations": {
+ "production": {
+ "buildTarget": "genkit-app:build:production"
+ },
+ "development": {
+ "buildTarget": "genkit-app:build:development"
+ }
+ },
+ "defaultConfiguration": "development"
+ },
+ "extract-i18n": {
+ "builder": "@angular-devkit/build-angular:extract-i18n"
+ },
+ "test": {
+ "builder": "@angular-devkit/build-angular:karma",
+ "options": {
+ "polyfills": ["zone.js", "zone.js/testing"],
+ "tsConfig": "tsconfig.spec.json",
+ "inlineStyleLanguage": "scss",
+ "assets": [
+ {
+ "glob": "**/*",
+ "input": "public"
+ }
+ ],
+ "styles": [
+ "@angular/material/prebuilt-themes/azure-blue.css",
+ "src/styles.scss"
+ ],
+ "scripts": []
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/samples/js-angular/genkit-app/package.json b/samples/js-angular/genkit-app/package.json
new file mode 100644
index 000000000..89cd74b7f
--- /dev/null
+++ b/samples/js-angular/genkit-app/package.json
@@ -0,0 +1,41 @@
+{
+ "name": "genkit-app",
+ "version": "0.0.0",
+ "scripts": {
+ "ng": "ng",
+ "start": "ng serve",
+ "build": "ng build",
+ "watch": "ng build --watch --configuration development",
+ "test": "ng test"
+ },
+ "private": true,
+ "dependencies": {
+ "@angular/animations": "^18.0.0",
+ "@angular/cdk": "^18.0.1",
+ "@angular/common": "^18.0.0",
+ "@angular/compiler": "^18.0.0",
+ "@angular/core": "^18.0.0",
+ "@angular/forms": "^18.0.0",
+ "@angular/material": "^18.0.1",
+ "@angular/platform-browser": "^18.0.0",
+ "@angular/platform-browser-dynamic": "^18.0.0",
+ "@angular/router": "^18.0.0",
+ "rxjs": "~7.8.0",
+ "tslib": "^2.3.0",
+ "zone.js": "~0.14.3",
+ "genkit": "^0.9.0-rc || ^0.9"
+ },
+ "devDependencies": {
+ "@angular-devkit/build-angular": "^18.0.2",
+ "@angular/cli": "^18.0.2",
+ "@angular/compiler-cli": "^18.0.0",
+ "@types/jasmine": "~5.1.0",
+ "jasmine-core": "~5.1.0",
+ "karma": "~6.4.0",
+ "karma-chrome-launcher": "~3.2.0",
+ "karma-coverage": "~2.2.0",
+ "karma-jasmine": "~5.1.0",
+ "karma-jasmine-html-reporter": "~2.1.0",
+ "typescript": "~5.4.2"
+ }
+}
diff --git a/samples/js-angular/genkit-app/public/favicon.ico b/samples/js-angular/genkit-app/public/favicon.ico
new file mode 100644
index 000000000..57614f9c9
Binary files /dev/null and b/samples/js-angular/genkit-app/public/favicon.ico differ
diff --git a/samples/js-angular/genkit-app/src/app/app.component.html b/samples/js-angular/genkit-app/src/app/app.component.html
new file mode 100644
index 000000000..0165f5b0f
--- /dev/null
+++ b/samples/js-angular/genkit-app/src/app/app.component.html
@@ -0,0 +1,36 @@
+
+
+
diff --git a/samples/js-angular/genkit-app/src/app/app.component.scss b/samples/js-angular/genkit-app/src/app/app.component.scss
new file mode 100644
index 000000000..0e0b80557
--- /dev/null
+++ b/samples/js-angular/genkit-app/src/app/app.component.scss
@@ -0,0 +1,67 @@
+/**
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+app-bar {
+ border-bottom: 1px solid var(--divider-color);
+ grid-area: header;
+}
+
+article {
+ grid-area: content;
+}
+
+.wrapper {
+ display: grid;
+ grid-template:
+ 'header' auto
+ 'content' 1fr
+ / 1fr;
+ height: 100vh;
+}
+
+.home-link {
+ align-items: center;
+ color: var(--mat-app-color);
+ display: flex;
+ gap: 8px;
+
+ img {
+ height: 22px;
+ padding-left: 4px;
+ }
+}
+
+.mat-toolbar {
+ background: #d7e3ff;
+ color: #005cbb;
+ gap: 4px;
+}
+
+nav {
+ --mdc-secondary-navigation-tab-container-height: 64px;
+ --mat-tab-header-divider-height: 0;
+ margin-left: 32px;
+}
+
+.preview-badge {
+ margin-left: 8px;
+
+ mat-icon {
+ font-size: 18px;
+ height: 18px;
+ width: 18px;
+ }
+}
diff --git a/samples/js-angular/genkit-app/src/app/app.component.spec.ts b/samples/js-angular/genkit-app/src/app/app.component.spec.ts
new file mode 100644
index 000000000..02dac7e71
--- /dev/null
+++ b/samples/js-angular/genkit-app/src/app/app.component.spec.ts
@@ -0,0 +1,47 @@
+/**
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { TestBed } from '@angular/core/testing';
+import { AppComponent } from './app.component';
+
+describe('AppComponent', () => {
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [AppComponent],
+ }).compileComponents();
+ });
+
+ it('should create the app', () => {
+ const fixture = TestBed.createComponent(AppComponent);
+ const app = fixture.componentInstance;
+ expect(app).toBeTruthy();
+ });
+
+ it(`should have the 'genkit-app' title`, () => {
+ const fixture = TestBed.createComponent(AppComponent);
+ const app = fixture.componentInstance;
+ expect(app.title).toEqual('genkit-app');
+ });
+
+ it('should render title', () => {
+ const fixture = TestBed.createComponent(AppComponent);
+ fixture.detectChanges();
+ const compiled = fixture.nativeElement as HTMLElement;
+ expect(compiled.querySelector('h1')?.textContent).toContain(
+ 'Hello, genkit-app'
+ );
+ });
+});
diff --git a/samples/js-angular/genkit-app/src/app/app.component.ts b/samples/js-angular/genkit-app/src/app/app.component.ts
new file mode 100644
index 000000000..39d4f4ea0
--- /dev/null
+++ b/samples/js-angular/genkit-app/src/app/app.component.ts
@@ -0,0 +1,45 @@
+/**
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { CommonModule } from '@angular/common';
+import { Component } from '@angular/core';
+import { MatButtonModule } from '@angular/material/button';
+import { MatIconModule } from '@angular/material/icon';
+import { MatTabNavPanel, MatTabsModule } from '@angular/material/tabs';
+import { MatToolbarModule } from '@angular/material/toolbar';
+import { MatTooltipModule } from '@angular/material/tooltip';
+import { RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
+
+@Component({
+ selector: 'app-root',
+ standalone: true,
+ imports: [
+ CommonModule,
+ MatToolbarModule,
+ RouterOutlet,
+ MatIconModule,
+ MatTabNavPanel,
+ MatButtonModule,
+ MatTabsModule,
+ MatToolbarModule,
+ MatTooltipModule,
+ RouterLink,
+ RouterLinkActive,
+ ],
+ templateUrl: './app.component.html',
+ styleUrl: './app.component.scss',
+})
+export class AppComponent {}
diff --git a/samples/js-angular/genkit-app/src/app/app.config.ts b/samples/js-angular/genkit-app/src/app/app.config.ts
new file mode 100644
index 000000000..3d04dfa9c
--- /dev/null
+++ b/samples/js-angular/genkit-app/src/app/app.config.ts
@@ -0,0 +1,29 @@
+/**
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
+import { provideRouter } from '@angular/router';
+
+import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
+import { routes } from './app.routes';
+
+export const appConfig: ApplicationConfig = {
+ providers: [
+ provideZoneChangeDetection({ eventCoalescing: true }),
+ provideRouter(routes),
+ provideAnimationsAsync(),
+ ],
+};
diff --git a/samples/js-angular/genkit-app/src/app/app.routes.ts b/samples/js-angular/genkit-app/src/app/app.routes.ts
new file mode 100644
index 000000000..bd70f607f
--- /dev/null
+++ b/samples/js-angular/genkit-app/src/app/app.routes.ts
@@ -0,0 +1,36 @@
+/**
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Routes } from '@angular/router';
+import { HomeComponent } from './home/home.component';
+import { ChatbotComponent } from './samples/chatbot/chatbot.component';
+import { StreamingJSONComponent } from './samples/streaming-json/streaming-json.component';
+
+export const routes: Routes = [
+ {
+ path: 'home',
+ component: HomeComponent,
+ },
+ {
+ path: 'samples/streaming-json',
+ component: StreamingJSONComponent,
+ },
+ {
+ path: 'samples/chatbot',
+ component: ChatbotComponent,
+ },
+ { path: '**', redirectTo: '/home' },
+];
diff --git a/samples/js-angular/genkit-app/src/app/home/home.component.html b/samples/js-angular/genkit-app/src/app/home/home.component.html
new file mode 100644
index 000000000..f57987dcc
--- /dev/null
+++ b/samples/js-angular/genkit-app/src/app/home/home.component.html
@@ -0,0 +1,32 @@
+
+
+
diff --git a/samples/js-angular/genkit-app/src/app/home/home.component.scss b/samples/js-angular/genkit-app/src/app/home/home.component.scss
new file mode 100644
index 000000000..da80fa8b3
--- /dev/null
+++ b/samples/js-angular/genkit-app/src/app/home/home.component.scss
@@ -0,0 +1,19 @@
+/**
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+.wrapper {
+ padding: 20px;
+}
diff --git a/samples/js-angular/genkit-app/src/app/home/home.component.spec.ts b/samples/js-angular/genkit-app/src/app/home/home.component.spec.ts
new file mode 100644
index 000000000..19eda49ae
--- /dev/null
+++ b/samples/js-angular/genkit-app/src/app/home/home.component.spec.ts
@@ -0,0 +1,38 @@
+/**
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { HomeComponent } from './home.component';
+
+describe('HomeComponent', () => {
+ let component: HomeComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [HomeComponent],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(HomeComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/samples/js-angular/genkit-app/src/app/home/home.component.ts b/samples/js-angular/genkit-app/src/app/home/home.component.ts
new file mode 100644
index 000000000..f1e1997c1
--- /dev/null
+++ b/samples/js-angular/genkit-app/src/app/home/home.component.ts
@@ -0,0 +1,27 @@
+/**
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Component } from '@angular/core';
+import { RouterLink, RouterLinkActive } from '@angular/router';
+
+@Component({
+ selector: 'app-home',
+ standalone: true,
+ imports: [RouterLink, RouterLinkActive],
+ templateUrl: './home.component.html',
+ styleUrl: './home.component.scss',
+})
+export class HomeComponent {}
diff --git a/samples/js-angular/genkit-app/src/app/samples/chatbot/chatbot.component.html b/samples/js-angular/genkit-app/src/app/samples/chatbot/chatbot.component.html
new file mode 100644
index 000000000..00829ec6d
--- /dev/null
+++ b/samples/js-angular/genkit-app/src/app/samples/chatbot/chatbot.component.html
@@ -0,0 +1,77 @@
+
+
+