Commit 8def655b authored by Yorrd's avatar Yorrd
Browse files

feat: mobile update

parent 37ad5c1b
Pipeline #1370 passed with stages
in 10 minutes and 38 seconds
......@@ -6,6 +6,10 @@ import { generateBase, generate } from '@adornis/design/generate';
import { spacing, tokens } from '@adornis/design/generate-base/tokens';
import { generateFontResets, fontReset } from 'imports/git_modules/design/generate-base/font-resets';
import { AdornisRouter } from 'imports/git_modules/adornis-components/a-router';
import { colors } from 'imports/client/globals';
import '../../imports/db';
import '@adornis/design/generate-base/molecules';
generateBase(spacing);
generateFontResets();
......@@ -25,16 +29,27 @@ generate('ccb', {
border: '1px solid grey',
},
button: {
...tokens(spacing).box,
background: 'lightgrey',
zIndex: '999',
background: colors.primary,
border: '2px solid ' + colors.primary,
padding: spacing.sm,
fontWeight: 'bold',
color: 'black',
height: '50px',
borderRadius: '25px',
color: colors.secondary,
cursor: 'pointer',
display: 'flex',
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
fontFamily: 'Barlow',
'[active]': {
background: 'lightgreen',
},
'[secondary]': {
background: colors.secondary,
color: colors.primary,
},
'[big]': { height: '75px', borderRadius: '37.5px' },
'[small]': { height: '40px', borderRadius: '20px' },
},
stack: {
...tokens(spacing).stack,
......@@ -47,7 +62,7 @@ generateFontResets('ccb', {
h3: { ...fontReset(1, 0.1, 'Barlow'), fontWeight: 'bold', fontSize: '20px' },
h4: { ...fontReset(1, 0.1, 'Barlow'), fontWeight: 'bold', fontSize: '18px' },
text: { ...fontReset(1, 0.1, 'Barlow'), fontSize: '16px' },
text: { ...fontReset(1, 0.1, 'Barlow'), fontSize: '18px' },
});
AdornisRouter.init(['/:page']);
......
......@@ -17,6 +17,7 @@ import '@adornis/adornis-components/a-drop-wrapper';
import '@material/mwc-checkbox';
import { virtualize } from '@adornis/base/when-seen';
import { AdornisDialog } from 'imports/git_modules/adornis-components/a-dialog';
import { listenDevice, BreakPoint } from 'imports/git_modules/base/window-size';
@element('ccb-contents-admin')
export class CCBContentsAdmin extends AdornisDBView<Content> {
......@@ -43,35 +44,38 @@ export class CCBContentsAdmin extends AdornisDBView<Content> {
public render({ fields, selectedFields, entities, sorting, filterForOwnEntities }: RenderProps<CCBContentsAdmin>) {
return html`
${this.searchBar()}
<a-stack size="md" style=${{ padding: `0 ${spacing.xxl}` }}>
${this.addNewEntry(fields)}
<ccb-text
>Du hast
${this.entities.pipe(
startWith(of([])),
switchMap(ents => ents.pipe(map(ents => ents.length))),
)}
von insgesamt ${Content.count({}).whenReady} Einträgen erstellt. Der durchschnittliche Nutzer erstellt
${Content.count({}).whenReady.pipe(
switchMap(count => AdornisUser.count({}).whenReady.pipe(map(userCount => (count / userCount).toFixed(2)))),
)}
Einträge.</ccb-text
>
${this.filterForOwn()} ${this.csvUploadButton()}
</a-stack>
<a-stack size="md" padding="xxl">
${rxRepeat(
combineLatest(entities, AdornisUser.currentUser).pipe(
map(([ents, user]) =>
ents
.filter(ent => !filterForOwnEntities || !user || user._id === ent.createdBy)
.sort(this.sortFunctionWithSortObject(sorting)),
<a-box padding="${listenDevice.pipe(map(device => (device < BreakPoint.laptop ? '' : 'xxl')))}">
<a-stack size="md" padding="lg">
${this.searchBar()} ${this.addNewEntry(fields)}
<ccb-text
>Du hast
${this.entities.pipe(
startWith(of([])),
switchMap(ents => ents.pipe(map(ents => ents.length))),
)}
von insgesamt ${Content.count({}).whenReady} Einträgen erstellt. Der durchschnittliche Nutzer erstellt
${Content.count({}).whenReady.pipe(
switchMap(count =>
AdornisUser.count({}).whenReady.pipe(map(userCount => (count / userCount).toFixed(2))),
),
)}
Einträge.</ccb-text
>
${this.filterForOwn()} ${this.csvUploadButton()}
</a-stack>
<a-stack size="md">
${rxRepeat(
combineLatest(entities, AdornisUser.currentUser).pipe(
map(([ents, user]) =>
ents
.filter(ent => !filterForOwnEntities || !user || user._id === ent.createdBy)
.sort(this.sortFunctionWithSortObject(sorting)),
),
),
),
(entity, index) => this.itemCard(entity, selectedFields, index),
)}
</a-stack>
(entity, index) => this.itemCard(entity, selectedFields, index),
)}
</a-stack>
</a-box>
`;
}
......@@ -137,6 +141,7 @@ export class CCBContentsAdmin extends AdornisDBView<Content> {
<ccb-stack horizontal centeraligned size="md">
<a-file-input @file-changed="${e => (fileToUpload = e.detail.file)}"></a-file-input>
<ccb-button
secondary
@click="${() => {
if (fileToUpload) {
entity.upload(fileToUpload);
......@@ -145,7 +150,8 @@ export class CCBContentsAdmin extends AdornisDBView<Content> {
>Datei hochladen</ccb-button
>
</ccb-stack>
<ccb-button @click="${() => entity.save()}"
<ccb-button @click="${() => entity.save()}"
secondary
>Diesen Inhalt als "aktuell" markieren (letzte Aktualisierung
${moment(entity.updateAt).format('DD.MM.YYYY HH:mm')})</ccb-button
>
......@@ -256,12 +262,7 @@ export class CCBContentsAdmin extends AdornisDBView<Content> {
horizontal
centerjustified
centeraligned
class="${this.css({
cursor: 'pointer',
borderStyle: 'solid',
borderColor: 'green',
borderRadius: '24px',
})}"
secondary
@click="${async () => {
const contents = await this.askForFile('*.csv');
await Promise.all(
......@@ -294,7 +295,7 @@ export class CCBContentsAdmin extends AdornisDBView<Content> {
return html`
<ccb-button
class="${this.css({ alignItems: 'center', display: 'flex', flexDirection: 'row' })}"
?active="${this.filterForOwnEntities.getValue()}"
?secondary="${!this.filterForOwnEntities.getValue()}"
@click="${() => this.filterForOwnEntities.next(!this.filterForOwnEntities.getValue())}"
><mwc-checkbox .checked="${this.filterForOwnEntities.getValue()}"></mwc-checkbox>Filter nach eigenen
Inhalten</ccb-button
......
import { element, html, ScopingElement } from 'imports/git_modules/base';
import { EmotionMixin } from '@adornis/design';
import { map } from 'rxjs/operators';
import { AdornisRouter } from 'imports/git_modules/adornis-components/a-router';
@element('ccb-landing-page')
export class CCBLandingPage extends EmotionMixin(ScopingElement) {
public render() {
return html`
${AdornisRouter.get('page').pipe(
map(page => {
switch (page) {
case '':
return html`
<a-stack size="xl">
<a-inline horizontal size="lg" padding="xl">
<a-stack size="md" class="${this.css({ flex: '1', minWidth: '300px' })}">
<ccb-text>Hi!</ccb-text>
<ccb-text
>Der Legal Chatbot gibt Ratsuchenden Antworten auf Ihre rechtlichen Fragen, die im Zuge der
Coronakrise entstanden sind.</ccb-text
>
<ccb-text
>Als Experte und Institution können Sie Ihre FAQs hier bereitstellen und diese regelmäßig
updaten. Über den Chatbot erhalten die Ratsuchenden Ihre Antworten</ccb-text
>
</a-stack>
<a-stack size="md" class="${this.css({ flex: '1', justifyContent: 'center', minWidth: '530px' })}">
<a-link href="https://t.me/CoronaLegalChatBot" target="_blank"
><ccb-button>Zum Legal Chatbot für&nbsp;<b>Ratsuchende</b></ccb-button></a-link
>
<a-link href="/contentadmin">
<ccb-button secondary
>Als&nbsp;<b>Institution</b>&nbsp;FAQs hinterlassen (bspw. LandesReg.)</ccb-button
>
</a-link>
<a-link href="/contentadmin">
<ccb-button secondary
>Als&nbsp;<b>Experte</b>&nbsp;FAQs hinterlassen (bspw. Kanzlei)</ccb-button
>
</a-link>
</a-stack>
</a-inline>
<img
src="/wirvsviruslogo.png"
class="${this.css({
marginLeft: 'auto',
marginRight: 'auto',
height: '100px',
width: 'auto',
display: 'block',
})}"
/>
</a-stack>
`;
case 'institutes':
return html`
<a-stack size="xl">
<a-stack size="lg" padding="xl">
<a-stack
size="md"
class="${this.css({ flex: '1', minWidth: '300px', justifyContent: 'flex-start' })}"
>
<ccb-text
>Diese Institutionen und Expert*Innen stellen uns proaktiv Ihre Inhalte zur Verfügung.</ccb-text
>
</a-stack>
<a-stack size="lg" class="${this.css({ flex: '2', minWidth: '300px', justifyContent: 'center' })}">
<a-stack size="md">
<ccb-button secondary big>Die&nbsp;<b>Institutionen</b></ccb-button>
<a-inline horizontal size="md"> <ccb-button small secondary>Platzhalter</ccb-button> </a-inline>
</a-stack>
<a-stack size="md">
<ccb-button big secondary>Die&nbsp;<b>Experten</b></ccb-button>
<a-stack size="xxs">
<a-inline horizontal size="md">
<ccb-button small secondary class="${this.css({ flex: '1' })}">
RA Pierre Daniel&nbsp;<b>Wittmann</b>, Deloitte Legal
</ccb-button>
<ccb-button small secondary class="${this.css({ flex: '1' })}">
IT Recht
</ccb-button>
</a-inline>
<a-inline horizontal size="md">
<ccb-button small secondary class="${this.css({ flex: '1' })}">
RA Miroslav&nbsp;<b>Georgiev</b>, GORG
</ccb-button>
<ccb-button small secondary class="${this.css({ flex: '1' })}">
KartellR, staatl. Beihilfen, Außenwirtschaft
</ccb-button>
</a-inline>
<a-inline horizontal size="md">
<ccb-button small secondary class="${this.css({ flex: '1' })}">
RAin Mirella&nbsp;<b>Endt-Eckhardt</b>
</ccb-button>
<ccb-button small secondary class="${this.css({ flex: '1' })}">
SteuerR
</ccb-button>
</a-inline>
<a-inline horizontal size="md">
<ccb-button small secondary class="${this.css({ flex: '1' })}">
RA Daniel&nbsp;<b>Herper</b>, FPS Rechtsanwälte
</ccb-button>
<ccb-button small secondary class="${this.css({ flex: '1' })}">
Insolvenzrecht und Restrukturierung
</ccb-button>
</a-inline>
<a-inline horizontal size="md">
<ccb-button small secondary class="${this.css({ flex: '1' })}">
RA Dr. Anette&nbsp;<b>Rosenkötter</b>, FPS Rechtsanwälte
</ccb-button>
<ccb-button small secondary class="${this.css({ flex: '1' })}">
BeihilfeR
</ccb-button>
</a-inline>
</a-stack>
</a-stack>
</a-stack>
</a-stack>
<img
src="/wirvsviruslogo.png"
class="${this.css({
marginLeft: 'auto',
marginRight: 'auto',
height: '100px',
width: 'auto',
display: 'block',
})}"
/>
</a-stack>
`;
}
}),
)}
`;
}
}
......@@ -37,14 +37,11 @@ export class ccbTopbar extends EmotionMixin(ScopingElement) {
@click="${() => this.fireEvent('toggle-sidebar')}"
><mwc-icon>menu</mwc-icon></ccb-h3
>
<ccb-text>${AdornisUser.currentUser.pipe(map(user => (user ? user.username : '')))}</ccb-text>
</ccb-stack>
<a-small-img
src="/coronalogo.png"
class="${this.css({ cursor: 'pointer', zIndex: '3' })}"
href="/"
></a-small-img>
<ccb-h4 @click="${() => AdornisUser.logout()}">Logout</ccb-h4>
<a-link href="/"> <img src="/coronalogo.png" style="cursor: pointer; height: 50px;" /> </a-link>
<ccb-h4 class="${this.css({ cursor: 'pointer', userSelect: 'none' })}" @click="${() => AdornisUser.logout()}"
>Logout</ccb-h4
>
</a-box>
`;
}
......
export const colors = {
primary: '',
primary: '#c73734ff',
secondary: 'white',
chatPrimary: '#9faeff',
chatSecondary: 'lightblue',
chatTextColor: 'black',
......
......@@ -5,6 +5,9 @@ import { spacing } from '@adornis/design/generate-base/tokens';
import { AdornisRouter } from '@adornis/adornis-components/a-router';
import { map } from 'rxjs/operators';
import { AdornisDialog } from '@adornis/adornis-components/a-dialog';
import { combineLatest } from 'rxjs';
import { AdornisUser } from '@adornis/users/db';
import { RenderProps } from '@adornis/base/reactive-lit-element';
import './ccb-topbar';
import './ccb-botbar';
......@@ -13,8 +16,7 @@ import './ccb-landingpage';
import './ccb-chat';
import './ccb-admin';
import './ccb-contents-admin';
import { combineLatest } from 'rxjs';
import { AdornisUser } from 'imports/git_modules/users/db';
import './ccb-landing-page';
@element('main-element')
export class MainElement extends Loader(AdornisLayout) {
......@@ -23,6 +25,20 @@ export class MainElement extends Loader(AdornisLayout) {
public topBarHeight = '85px';
public botBarHeight = '32px';
// public render(renderProps: RenderProps<MainElement>) {
// return html`
// ${AdornisRouter.get('page').pipe(
// map(page =>
// page
// ? super.render(renderProps)
// : html`
// <ccb-landing-page></ccb-landing-page>
// `,
// ),
// )}
// `;
// }
public topBar() {
return html`
<ccb-topbar @toggle-sidebar="${() => this.toggleLeftSidebar()}"></ccb-topbar>
......@@ -30,11 +46,6 @@ export class MainElement extends Loader(AdornisLayout) {
}
public leftSidebar() {
return html`
<div
style="position: absolute; top: 0; left: 0; width: 100%; background-color: red; color: white; font-weight: bold; z-index: 10000;"
>
Hier nicht eintragen, Testumgebung
</div>
<div class="${this.css({ height: '100%', background: 'white', paddingTop: '12px' })}">
<a-stack size="md" padding="md" class="${this.css({ background: 'white' })}">
<ccb-h3
......@@ -44,9 +55,13 @@ export class MainElement extends Loader(AdornisLayout) {
>
<a-stack endjustified size="md">
<a-link href="/"><ccb-h4>Startseite</ccb-h4></a-link>
<a-link href="/admin"><ccb-h4>Chat-Admin</ccb-h4></a-link>
<a-link href="/contentadmin"><ccb-h4>Inhalt-Admin</ccb-h4></a-link>
<a-link class="${this.css({ cursor: 'pointer' })}" href="/"><ccb-h4>Startseite</ccb-h4></a-link>
<a-link class="${this.css({ cursor: 'pointer' })}" href="/institutes"
><ccb-h4>Institutionen</ccb-h4></a-link
>
<a-link class="${this.css({ cursor: 'pointer' })}" href="/contentadmin"
><ccb-h4>Inhalt-Admin</ccb-h4></a-link
>
<ccb-h4 class="${this.css({ cursor: 'pointer' })}" href="/datenschutz">Datenschutz</ccb-h4>
<ccb-h4
class="${this.css({ cursor: 'pointer' })}"
......@@ -83,27 +98,23 @@ export class MainElement extends Loader(AdornisLayout) {
public content() {
return html`
${Meteor.absoluteUrl().includes('livelinks')
? html`
<div
style="position: absolute; top: 0; left: 0; width: 100%; background-color: red; color: white; font-weight: bold;"
>
Hier nicht eintragen, Testumgebung
</div>
`
: ''}
${combineLatest(AdornisRouter.get('page'), AdornisUser.currentUser).pipe(
map(([page, user]) => {
if (!user) return this.login();
switch (page) {
case '':
case 'institutes':
return html`
<a-box padding="md"
><a-stack size="md">
<ccb-text> <ccb-h1>Willkommen zum Corona Chatbot</ccb-h1></ccb-text>
<ccb-text
>Hier erhälst du unkompliziert und schnell, professionelle Antworten zu deinen rechtlichen Fragen
rund um die Sonderzustände hervorgerufen durch die Corona Krise. Unsere Experten haben für dich
alle wichtigen Informationen zusammengetragen und untersützen dich natürlich auch gerne
persönlich.</ccb-text
>
<ccb-text
>Schreib uns einfach deine Frage und unser Chatbot wird dir, die für dich interessanten
Informationen liefern.</ccb-text
>
</a-stack>
</a-box>
<ccb-landing-page></ccb-landing-page>
`;
case 'admin':
return html`
......
import { Entity, Field } from '@adornis/baseql/decorators';
import { AdornisUser } from '@adornis/users/db';
@Entity()
export class CCBUser extends AdornisUser {
public static _name = 'CCBUser';
@Field(String)
public contactName?: string;
@Field(String)
public contactMail?: string;
@Field(String)
public organization?: string;
@Field(Boolean)
public verified?: boolean;
}
export * from './contents';
export * from './ccbuser';
Subproject commit 31ac85b6588b939d72fc9160c309fdd875ad41c0
Subproject commit ffb2da949538ebd0316522010551790933777b7a
public/coronalogo.png

19.9 KB | W: | H:

public/coronalogo.png

133 KB | W: | H:

public/coronalogo.png
public/coronalogo.png
public/coronalogo.png
public/coronalogo.png
  • 2-up
  • Swipe
  • Onion skin
import { Card, Suggestion, WebhookClient } from 'dialogflow-fulfillment';
import { Card, Suggestion, WebhookClient, Image } from 'dialogflow-fulfillment';
import express from 'express';
import { WebApp } from 'meteor/webapp';
import { Collection } from 'mongodb';
import { files } from '@adornis/file-utils/db';
import { AdornisUser } from 'imports/git_modules/users/db';
import { CCBUser } from 'imports/db/ccbuser';
process.env.DEBUG = 'dialogflow:debug'; // enables lib debugging statements
......@@ -19,18 +21,52 @@ expressApp.post('/api/dialogflow-webhook', (request, response, next) => {
async function yourFunctionHandler(agent: WebhookClient) {
console.log(JSON.stringify(agent.originalRequest, null, 2));
// answer to disclaimer
if (agent.contexts.find(el => el.name === 'asked-for-disclaimer')) {
if (agent.query.includes('/land')) {
agent.add(
'<b>Disclaimer:</b> \nDer Legal Chatbot richtet sich an Unternehmer und Unternehmen sowie juristische Laien und Interessierte, die allgemeine rechtliche Informationen suchen. Um für diese Zielgruppe rechtliche Sachverhalte verständlich darstellen zu können, werden unsere Informationen oftmals vereinfacht und verkürzt dargestellt. Die Legal Chatbot Antworten dienen insofern lediglich der Information und geben die Auffassung und Meinung der jeweiligen Autor*innen/ Institutionen wieder. Die Legal Chatbot Antworten können daher nicht als Rechtsberatung angesehen werden, da konkrete Fälle zu unterschiedlich sind, um sie anhand einer Legal Chatbot Antwort zu lösen zu können. Die Legal Chatbot Beiträge spiegeln lediglich die Rechtslage zum jeweiligen Veröffentlichungsdatum wieder.',
'Land zu Deutschland gewechselt. Wir arbeiten an der Datengrundlage für weitere Länder, im Moment in der Schweiz.',
);
agent.add('Wie kann ich dir helfen?');
return;
}
// answer to disclaimer
if (agent.contexts.find(el => el.name === 'asked-for-disclaimer')) {
if (agent.query === 'Ja') {
agent.add(
'Deiner IP nach zu urteilen bist du in Deutschland ansässig. Wenn das nicht stimmt, wechsle das Land mit /land',
);
agent.add('Wie kann ich dir helfen?');
} else {
agent.add(
'Disclaimer: \nDer Legal Chatbot richtet sich an Unternehmer und Unternehmen sowie juristische Laien und Interessierte, die allgemeine rechtliche Informationen suchen. Um für diese Zielgruppe rechtliche Sachverhalte verständlich darstellen zu können, werden unsere Informationen oftmals vereinfacht und verkürzt dargestellt. Die Legal Chatbot Antworten dienen insofern lediglich der Information und geben die Auffassung und Meinung der jeweiligen Autor*innen/ Institutionen wieder. Die Legal Chatbot Antworten können daher nicht als Rechtsberatung angesehen werden, da konkrete Fälle zu unterschiedlich sind, um sie anhand einer Legal Chatbot Antwort zu lösen zu können. Die Legal Chatbot Beiträge spiegeln lediglich die Rechtslage zum jeweiligen Veröffentlichungsdatum wieder.',
);
agent.add(
'Deiner IP nach zu urteilen bist du in Deutschland ansässig. Wenn das nicht stimmt, wechsle das Land mit /land',
);
agent.add('Wie kann ich dir helfen?');
}
return;
}
if (agent.query === 'jetzt mit Menschen schreiben') {
agent.add('Du wirst schnellstmöglich verbunden. Bitte hab Verständnis, falls es zu Wartezeiten kommt :)');
agent.add('Hallo! Ich bin Jürgen, Fachanwalt für Steuerrecht, wie kann ich dir helfen?');
return;
}
if (agent.query === 'weiter suchen' || agent.query === 'noch einmal suchen') {
return;
}
if (agent.query.includes('danke')) {
agent.add('gerne! :) wie können wir dir noch helfen?');
return;
}
switch (agent.intent) {
case 'welcome disclaimer': {
agent.add('Hallo');
agent.add('Möchtest du den Disclaimer zur Haftbarkeit anschauen?');
agent.add('Kennst du schon den Disclaimer zur Haftung und zum Datenschutz schon?');
agent.add(new Suggestion('Ja'));
agent.add(new Suggestion('Nein'));
agent.setContext({ name: 'asked-for-disclaimer', lifespan: 1 });
......@@ -43,18 +79,15 @@ expressApp.post('/api/dialogflow-webhook', (request, response, next) => {
const foundContents = await (files.rawCollection() as Collection)
.find({ $text: { $search: agent.query } })
.project({ score: { $meta: 'textScore' } })
// .filter({ score: { $gte: 0.7 } })
.sort({ score: { $meta: 'textScore' } })
.toArray();
console.log(foundContents.map(x => x.score));
// if (foundContents.length > 20) {
// agent.add(
// `Hierzu gibt es sehr viele Ergebnisse (${foundContents.length}) und keine Überkategorie, versuch deine Frage ein wenig genauer zu formulieren :) (wenn wir es schaffen, hier ein carousel von Möglichkeits-Previews anzeigen)`,
// );
// return;
// }
const content = foundContents[0];
if (!content) {
agent.add('Da kann ich dir nicht helfen');
agent.add('Da kann ich dir leider nicht weiterhelfen.');
agent.add(new Suggestion('jetzt mit Menschen schreiben'));
agent.add(new Suggestion('weiter suchen'));
return;
}
......@@ -62,19 +95,23 @@ expressApp.post('/api/dialogflow-webhook', (request, response, next) => {
new Card({
title: content.Frage || 'kein Titel',
// imageUrl: 'https://developers.google.com/actions/images/badges/XPM_BADGING_GoogleAssistant_VER.png',
text: content.Antwort?.replace(/<[^>]*>/gi, ''),
text:
content.Antwort?.replace(/<[^>]*>/gi, '')
.replace(/[\s]+$|^[\s]+/g, '')
.replace(/&nbsp;/gi, '') + '.',
buttonText: 'Weiterführende Informationen',
buttonUrl: content.Quelle || 'https://google.com/',
}),
);
const creator = (await AdornisUser.findOne({ _id: content.createdBy }).result) as CCBUser;
if (!!creator && !!creator.verified) {
agent.add('von ' + creator.contactName + ' (' + creator.contactMail + ', ' + creator.organization + ')');
} else {
agent.add('Diese Nachricht wurde aus automatisch generierten Inhalten erzeugt');
}
agent.add('Konnten wir dir damit weiterhelfen?');
agent.add(new Suggestion('Ja, danke!'));
agent.add(new Suggestion('nicht was ich meinte'));
agent.add(new Suggestion('Suche verfeinern'));
// agent.add(new Suggestion(`Quick Reply`));
// agent.add(new Suggestion(`Suggestion`));
// agent.setContext({ name: 'weather', lifespan: 2, parameters: { city: 'Rome' } });
agent.add(new Suggestion('noch einmal suchen'<