mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-10-19 22:38:00 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e973b51f75 | ||
|
|
c9474c55b7 | ||
|
|
c0b7016c3a | ||
|
|
78315195cb | ||
|
|
6040e88dad | ||
|
|
ad3d8cbb3a | ||
|
|
c836bd7f33 | ||
|
|
9d71978feb | ||
|
|
940459ca46 | ||
|
|
09d67bc1c2 | ||
|
|
1681405fb9 | ||
|
|
be408a6aee | ||
|
|
cf975f2a6c | ||
|
|
1df1d3f328 | ||
|
|
ed14525390 | ||
|
|
14caf550ec | ||
|
|
f50e041640 | ||
|
|
ebe00812e2 | ||
|
|
ae72c50236 | ||
|
|
48874d617a | ||
|
|
63be312bb4 | ||
|
|
06f66fdab2 | ||
|
|
b9a65d9ca0 | ||
|
|
e4b1ea32fb | ||
|
|
e9975a5fbc | ||
|
|
2ef4d9d53f | ||
|
|
33a2728644 | ||
|
|
a54c0bdbbf | ||
|
|
d6521850e9 | ||
|
|
258bc7a338 | ||
|
|
187663bbf2 | ||
|
|
8060fc01dd | ||
|
|
b2ca49ab92 | ||
|
|
5e445f09fa | ||
|
|
6f5aecd92b | ||
|
|
317d3c9eec | ||
|
|
0acb3a20a2 | ||
|
|
3b93841982 | ||
|
|
38a45955fb | ||
|
|
caec3bfd2c | ||
|
|
12b3d6fc3d | ||
|
|
21927754b6 | ||
|
|
2131bf1817 | ||
|
|
8cbef49d89 | ||
|
|
cdf717ad5f | ||
|
|
02b6f526da | ||
|
|
a365539282 | ||
|
|
7250b270f5 | ||
|
|
d437d08640 | ||
|
|
61f7a3dd72 | ||
|
|
55d7895f58 | ||
|
|
e2859b3d76 | ||
|
|
0a951ead3e | ||
|
|
432ae30fdf | ||
|
|
799112eb9d | ||
|
|
f806ee28c5 | ||
|
|
32ff143b21 | ||
|
|
90a1f681c5 | ||
|
|
99fe9f3a4f | ||
|
|
cf8a9800f3 | ||
|
|
f57e971f80 | ||
|
|
0d104f5abc | ||
|
|
ba6b8d2b3c | ||
|
|
1c72c4d6ec | ||
|
|
4ca5aba58f | ||
|
|
c4d2574508 | ||
|
|
ceac9cefe5 | ||
|
|
ebe2ad62c6 | ||
|
|
9c0dea9927 | ||
|
|
d2a2d658e6 | ||
|
|
d82427ba56 | ||
|
|
386121d198 | ||
|
|
f9245d52c2 | ||
|
|
9fa2a1a135 | ||
|
|
69288699b3 | ||
|
|
826d15729b | ||
|
|
2ae40c59a3 | ||
|
|
119100bbce | ||
|
|
f92fab24b3 | ||
|
|
671abf3b07 | ||
|
|
d0a366258a | ||
|
|
58d7478f3a | ||
|
|
e8216b70a5 | ||
|
|
152d5f9b4f | ||
|
|
f1104c5e09 | ||
|
|
3ccb097705 | ||
|
|
b9416e66c3 | ||
|
|
9f576e8572 | ||
|
|
629613f75a | ||
|
|
abbb8c1650 | ||
|
|
d2ac4e26b0 | ||
|
|
bd97c0fb4b | ||
|
|
8bd54e6d61 | ||
|
|
e419c5561b | ||
|
|
24a73e92e1 | ||
|
|
832e054dbf | ||
|
|
4703ab04ab | ||
|
|
c1d5be63cc | ||
|
|
5d3ea3d57a | ||
|
|
217b9c4db3 | ||
|
|
e9c805f0df | ||
|
|
11475ea4af | ||
|
|
46a7e98141 | ||
|
|
0d6ccbd237 | ||
|
|
efb30cad87 | ||
|
|
fe33867cb6 | ||
|
|
1b1989e8eb | ||
|
|
afa5df894e | ||
|
|
c8d67b1e4c | ||
|
|
e717b84fb4 | ||
|
|
2463c85b77 | ||
|
|
cbf34580b3 | ||
|
|
e60f815d77 | ||
|
|
a19a6b2188 | ||
|
|
d2dd90d089 | ||
|
|
37e113af0e | ||
|
|
776fa8ac58 | ||
|
|
8715054dda | ||
|
|
95da4c6410 | ||
|
|
92f5b49543 | ||
|
|
e6be9a593e | ||
|
|
375be805a1 | ||
|
|
b99866b8fe | ||
|
|
e9998348c5 | ||
|
|
0ad7b8d4dd | ||
|
|
4175387a93 | ||
|
|
1cac1a7913 | ||
|
|
5fa2f3c822 | ||
|
|
5608b48a2d | ||
|
|
bf01fbd706 | ||
|
|
4722c1baa1 | ||
|
|
222c1e929f | ||
|
|
2a648105e6 | ||
|
|
09f5b83e66 | ||
|
|
7a5f2d4aec | ||
|
|
0d1164c0eb | ||
|
|
54588fc92d | ||
|
|
a5bdb3267b | ||
|
|
d7c124926b | ||
|
|
00f45f8998 | ||
|
|
d0d9fa3d22 | ||
|
|
b3dd213538 | ||
|
|
9dfabb3c55 | ||
|
|
d0dbbccd56 | ||
|
|
75576129fe | ||
|
|
2efd08dbbc | ||
|
|
4bd22108de | ||
|
|
4ea767cb45 | ||
|
|
11846e279c | ||
|
|
c861d71383 | ||
|
|
6fbf32d8a2 | ||
|
|
4216a29325 | ||
|
|
45770c2cd7 | ||
|
|
57905d5510 | ||
|
|
e999aab7c0 | ||
|
|
c47d3893ff | ||
|
|
09c1b77414 | ||
|
|
ea083fdec2 | ||
|
|
ceabcdb26e | ||
|
|
a25793ff11 | ||
|
|
8d8a47b43b | ||
|
|
2889581702 | ||
|
|
12602ba8dd | ||
|
|
e4b8487945 | ||
|
|
fc64c0b505 | ||
|
|
47f5756e5a | ||
|
|
e4ef4bc06e | ||
|
|
ae17104886 | ||
|
|
2d82fcc5e1 | ||
|
|
69a6fcf094 | ||
|
|
4aaa2fcbea | ||
|
|
d855a33e6f | ||
|
|
ca16a749fd | ||
|
|
bcdbf7dcbd | ||
|
|
537a608e7a | ||
|
|
a6c5b3de23 | ||
|
|
3fd0d4d5c1 | ||
|
|
5efb6b0158 | ||
|
|
1b3fff67ec | ||
|
|
3409872090 | ||
|
|
18718a6c47 | ||
|
|
34884859d7 | ||
|
|
50bf0baff7 | ||
|
|
83fe126c9f | ||
|
|
37b38e6a40 | ||
|
|
1d1af91f3e | ||
|
|
a18cb22263 |
No files matched your search
@@ -11,3 +11,7 @@ media/**/*.pptx filter=lfs diff=lfs merge=lfs -text
|
||||
media/**/*.png filter=lfs diff=lfs merge=lfs -text
|
||||
media/**/*.ico filter=lfs diff=lfs merge=lfs -text
|
||||
media/**/*.icns filter=lfs diff=lfs merge=lfs -text
|
||||
*.woff2 filter=lfs diff=lfs merge=lfs -text
|
||||
*.afpub filter=lfs diff=lfs merge=lfs -text
|
||||
*.afdesign filter=lfs diff=lfs merge=lfs -text
|
||||
*.afphoto filter=lfs diff=lfs merge=lfs -text
|
||||
@@ -249,18 +249,6 @@ jobs:
|
||||
cd publish/dotnet
|
||||
mv mindworkAIStudio.exe "../../app/MindWork AI Studio/bin/dist/mindworkAIStudioServer${{ matrix.dotnet_name_postfix }}"
|
||||
|
||||
- name: Create parts for the Rust cache key (Unix)
|
||||
if: matrix.platform != 'windows-latest'
|
||||
run: |
|
||||
cd runtime
|
||||
echo "CARGO_LOCK_HASH=${{ hashFiles('**/Cargo.lock') }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Create parts for the Rust cache key (Windows)
|
||||
if: matrix.platform == 'windows-latest'
|
||||
run: |
|
||||
cd runtime
|
||||
echo "CARGO_LOCK_HASH=${{ hashFiles('**/Cargo.lock') }}" >> $env:GITHUB_ENV
|
||||
|
||||
- name: Cache Rust
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
@@ -272,17 +260,7 @@ jobs:
|
||||
~/.rustup/toolchains
|
||||
runtime/target
|
||||
|
||||
# When the entire key matches, Rust might just create the bundles using the current .NET build:
|
||||
key: target-${{ matrix.dotnet_runtime }}-rust-${{ env.RUST_VERSION }}-dependencies-${{ env.CARGO_LOCK_HASH }}
|
||||
|
||||
# - 1st key: the Rust runtime dependencies changed. Anyway, we might be able to re-use many previously built packages.
|
||||
#
|
||||
# - No match: alright, a new Rust version was released. Sadly, we cannot re-use anything now. Why?
|
||||
# The updated Rust compiler might mitigate some bugs or vulnerabilities. In order to apply
|
||||
# these changes to our app, we have to re-compile everything. That's the reason why it makes
|
||||
# no sense to use more parts for the cache key, like Tauri or Tauri build versions.
|
||||
restore-keys: |
|
||||
target-${{ matrix.dotnet_runtime }}-rust-${{ env.RUST_VERSION }}-dependencies-
|
||||
key: target-${{ matrix.dotnet_runtime }}-rust-${{ env.RUST_VERSION }}
|
||||
|
||||
- name: Setup Rust (stable)
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
@@ -300,7 +278,7 @@ jobs:
|
||||
if: matrix.platform != 'windows-latest'
|
||||
run: |
|
||||
if ! cargo tauri --version > /dev/null 2>&1; then
|
||||
cargo install tauri-cli
|
||||
cargo install --version 1.6.2 tauri-cli
|
||||
else
|
||||
echo "Tauri is already installed"
|
||||
fi
|
||||
@@ -309,7 +287,7 @@ jobs:
|
||||
if: matrix.platform == 'windows-latest'
|
||||
run: |
|
||||
if (-not (cargo tauri --version 2>$null)) {
|
||||
cargo install tauri-cli
|
||||
cargo install --version 1.6.2 tauri-cli
|
||||
} else {
|
||||
Write-Output "Tauri is already installed"
|
||||
}
|
||||
@@ -392,7 +370,7 @@ jobs:
|
||||
|
||||
build_linux_arm64:
|
||||
name: Build app (linux-arm64)
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
needs: read_metadata
|
||||
env:
|
||||
SKIP: false # allows disabling this long-running job temporarily
|
||||
@@ -478,30 +456,13 @@ jobs:
|
||||
cd publish/dotnet
|
||||
mv mindworkAIStudio "../../app/MindWork AI Studio/bin/dist/mindworkAIStudioServer-aarch64-unknown-linux-gnu"
|
||||
|
||||
- name: Create parts for the Rust cache key
|
||||
if: ${{ env.SKIP != 'true' }}
|
||||
run: |
|
||||
cd runtime
|
||||
echo "CARGO_LOCK_HASH=${{ hashFiles('**/Cargo.lock') }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Cache linux arm64 runner image
|
||||
if: ${{ env.SKIP != 'true' }}
|
||||
uses: actions/cache@v4
|
||||
id: linux_arm_cache
|
||||
with:
|
||||
path: $RUNNER_TEMP/linux_arm_qemu_cache.img
|
||||
|
||||
# When the entire key matches, Rust might just create the bundles using the current .NET build:
|
||||
key: target-linux-arm64-rust-${{ env.RUST_VERSION }}-dependencies-${{ env.CARGO_LOCK_HASH }}
|
||||
|
||||
# - 1st key: the Rust runtime dependencies changed. Anyway, we might be able to re-use many previously built packages.
|
||||
#
|
||||
# - No match: alright, a new Rust version was released. Sadly, we cannot re-use anything now. Why?
|
||||
# The updated Rust compiler might mitigate some bugs or vulnerabilities. In order to apply
|
||||
# these changes to our app, we have to re-compile everything. That's the reason why it makes
|
||||
# no sense to use more parts for the cache key, like Tauri or Tauri build versions.
|
||||
restore-keys: |
|
||||
target-linux-arm64-rust-${{ env.RUST_VERSION }}-dependencies-
|
||||
path: ${{ runner.temp }}/linux_arm_qemu_cache.img
|
||||
key: target-linux-arm64-rust-${{ env.RUST_VERSION }}
|
||||
|
||||
- name: Build linux arm runner image
|
||||
uses: pguyot/arm-runner-action@v2
|
||||
@@ -539,12 +500,12 @@ jobs:
|
||||
apt-get install --yes libwebkit2gtk-4.0-dev build-essential libssl-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev
|
||||
|
||||
# Setup Tauri:
|
||||
cargo install tauri-cli
|
||||
cargo install --version 1.6.2 tauri-cli
|
||||
|
||||
- name: Add the built runner image to the cache
|
||||
if: ${{ steps.linux_arm_cache.outputs.cache-hit != 'true' && env.SKIP != 'true' }}
|
||||
run: |
|
||||
mv ${{ steps.build-linux-arm-runner.outputs.image }} $RUNNER_TEMP/linux_arm_qemu_cache.img
|
||||
mv ${{ steps.build-linux-arm-runner.outputs.image }} ${{ runner.temp }}/linux_arm_qemu_cache.img
|
||||
|
||||
- name: Build Tauri project
|
||||
if: ${{ env.SKIP != 'true' }}
|
||||
@@ -552,11 +513,12 @@ jobs:
|
||||
id: build-linux-arm
|
||||
|
||||
with:
|
||||
base_image: file://$RUNNER_TEMP/linux_arm_qemu_cache.img
|
||||
base_image: file://${{ runner.temp }}/linux_arm_qemu_cache.img
|
||||
cpu: cortex-a53
|
||||
optimize_image: false
|
||||
copy_artifact_path: runtime
|
||||
copy_artifact_dest: result
|
||||
bind_mount_repository: true
|
||||
|
||||
#
|
||||
# We do not need to set the PRIVATE_PUBLISH_KEY and PRIVATE_PUBLISH_KEY_PASSWORD here,
|
||||
@@ -573,19 +535,17 @@ jobs:
|
||||
export CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse
|
||||
source "$HOME/.cargo/env"
|
||||
cd runtime
|
||||
|
||||
# Try to restore the Rust cache from previous build:
|
||||
mkdir -p /rust-cache/target
|
||||
rm -fr target
|
||||
cp -Rp /rust-cache/target target
|
||||
|
||||
cargo tauri build --target aarch64-unknown-linux-gnu --bundles deb
|
||||
|
||||
- name: Debug
|
||||
if: ${{ env.SKIP != 'true' }}
|
||||
run: |
|
||||
echo "Current directory: $(pwd)"
|
||||
ls -lhat
|
||||
|
||||
echo "Searching for linux_arm_qemu_cache.img"
|
||||
find $RUNNER_TEMP -name 'linux_arm_qemu_cache.img' -print 2>/dev/null
|
||||
|
||||
echo "Searching for mind-work-ai-studio_*.deb"
|
||||
find . -name 'mind-work-ai-studio_*.deb' -print 2>/dev/null
|
||||
|
||||
# Save the built libraries for the next job:
|
||||
rm -fr /rust-cache/target
|
||||
cp -Rp target /rust-cache
|
||||
|
||||
- name: Update the runner image to cache the Rust runtime build
|
||||
if: ${{ env.SKIP != 'true' }}
|
||||
@@ -729,6 +689,9 @@ jobs:
|
||||
# Replace newlines in changelog with \n
|
||||
changelog=$(echo "$CHANGELOG" | awk '{printf "%s\\n", $0}')
|
||||
|
||||
# Escape double quotes in changelog:
|
||||
changelog=$(echo "$changelog" | sed 's/"/\\"/g')
|
||||
|
||||
# Create the latest.json file:
|
||||
cat <<EOOOF > $GITHUB_WORKSPACE/release/assets/latest.json
|
||||
{
|
||||
@@ -794,7 +757,11 @@ jobs:
|
||||
|
||||
- name: Append scan results to changelog
|
||||
run: |
|
||||
changelog="${{ env.CHANGELOG }}"
|
||||
changelog=$(cat <<'EOOOOOOOF'
|
||||
${{ env.CHANGELOG }}
|
||||
EOOOOOOOF
|
||||
)
|
||||
|
||||
links="${{ steps.virus_total.outputs.analysis }}"
|
||||
|
||||
# Create a temporary file for the changelog:
|
||||
@@ -838,7 +805,7 @@ jobs:
|
||||
- name: Create release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
prerelease: false
|
||||
prerelease: true
|
||||
draft: false
|
||||
make_latest: true
|
||||
body: ${{ env.CHANGELOG }}
|
||||
|
||||
@@ -147,3 +147,4 @@ orleans.codegen.cs
|
||||
**/.idea/**/dynamic.xml
|
||||
**/.idea/**/uiDesigner.xml
|
||||
**/.idea/**/dbnavigator.xml
|
||||
**/.vs
|
||||
@@ -0,0 +1,22 @@
|
||||
# This CITATION.cff file was generated with cffinit.
|
||||
# Visit https://bit.ly/cffinit to generate yours today!
|
||||
|
||||
cff-version: 1.2.0
|
||||
title: AI Studio
|
||||
message: >-
|
||||
When you want to cite AI Studio in your scientific work,
|
||||
please use these metadata.
|
||||
type: software
|
||||
authors:
|
||||
- given-names: Thorsten
|
||||
family-names: Sommer
|
||||
email: thorsten.sommer@dlr.de
|
||||
affiliation: Deutsches Zentrum für Luft- und Raumfahrt (DLR)
|
||||
orcid: 'https://orcid.org/0000-0002-3264-9934'
|
||||
- name: Open Source Community
|
||||
repository-code: 'https://github.com/MindWorkAI/AI-Studio'
|
||||
url: 'https://mindworkai.org/'
|
||||
keywords:
|
||||
- LLM
|
||||
- AI
|
||||
- Orchestration
|
||||
+1
-1
@@ -6,7 +6,7 @@ FSL-1.1-MIT
|
||||
|
||||
## Notice
|
||||
|
||||
Copyright 2024 Thorsten Sommer
|
||||
Copyright 2025 Thorsten Sommer
|
||||
|
||||
## Terms and Conditions
|
||||
|
||||
|
||||
@@ -1,14 +1,66 @@
|
||||
# MindWork AI Studio
|
||||

|
||||
Are you new here? [Read here](#what-is-ai-studio) what AI Studio is.
|
||||
|
||||
MindWork AI Studio is a desktop application available for macOS, Windows, and Linux. It provides a unified chat interface for Large Language Models (LLMs). You bring your own API key for the respective LLM provider to use the models. The API keys are securely stored by the operating system.
|
||||
## News
|
||||
Things we are currently working on:
|
||||
|
||||
- Since November 2024: Work on RAG (integration of your data and files) has begun. We will support the integration of local and external data sources. We need to implement the following runtime (Rust) and app (.NET) steps:
|
||||
|
||||
- [x] ~~Runtime: Restructuring the code into meaningful modules (PR [#192](https://github.com/MindWorkAI/AI-Studio/pull/192))~~
|
||||
- [x] ~~Define the [External Retrieval Interface (ERI)](https://github.com/MindWorkAI/ERI) as a contract for integrating arbitrary external data (PR [#1](https://github.com/MindWorkAI/ERI/pull/1))~~
|
||||
- [x] ~~App: Metadata for providers (which provider offers embeddings?) (PR [#205](https://github.com/MindWorkAI/AI-Studio/pull/205))~~
|
||||
- [x] ~~App: Add an option to show preview features (PR [#222](https://github.com/MindWorkAI/AI-Studio/pull/222))~~
|
||||
- [x] ~~App: Configure embedding providers (PR [#224](https://github.com/MindWorkAI/AI-Studio/pull/224))~~
|
||||
- [x] ~~App: Implement an [ERI](https://github.com/MindWorkAI/ERI) server coding assistant (PR [#231](https://github.com/MindWorkAI/AI-Studio/pull/231))~~
|
||||
- [x] ~~App: Management of data sources (local & external data via [ERI](https://github.com/MindWorkAI/ERI)) (PR [#259](https://github.com/MindWorkAI/AI-Studio/pull/259), [#273](https://github.com/MindWorkAI/AI-Studio/pull/273))~~
|
||||
- [ ] Runtime: Extract data from txt / md / pdf / docx / xlsx files
|
||||
- [ ] (*Optional*) Runtime: Implement internal embedding provider through [fastembed-rs](https://github.com/Anush008/fastembed-rs)
|
||||
- [ ] App: Implement external embedding providers
|
||||
- [ ] App: Implement the process to vectorize one local file using embeddings
|
||||
- [ ] Runtime: Integration of the vector database [LanceDB](https://github.com/lancedb/lancedb)
|
||||
- [ ] App: Implement the continuous process of vectorizing data
|
||||
- [ ] App: Define a common retrieval context interface for the integration of RAG processes in chats
|
||||
- [ ] App: Define a common augmentation interface for the integration of RAG processes in chats
|
||||
- [ ] App: Integrate data sources in chats
|
||||
|
||||
|
||||
- Since September 2024: Experiments have been started on how we can work on long texts with AI Studio. Let's say you want to write a fantasy novel or create a complex project proposal and use LLM for support. The initial experiments were promising, but not yet satisfactory. We are testing further approaches until a satisfactory solution is found. The current state of our experiment is available as an experimental preview feature through your app configuration. Related PR: ~~[#167](https://github.com/MindWorkAI/AI-Studio/pull/167), [#226](https://github.com/MindWorkAI/AI-Studio/pull/226)~~.
|
||||
|
||||
|
||||
Other News:
|
||||
|
||||
- October 2024: We've found the first two financial supporters. Huge thanks to `richard-stanton` and `peerschuett` for backing the project. Thanks for having the courage to be the first to support us.
|
||||
|
||||
- October 2024: The [German Aerospace Center (DLR)](https://en.wikipedia.org/wiki/German_Aerospace_Center) ([Website](https://www.dlr.de/en)) will use AI Studio at least within the scope of one project and will also contribute to its further development. This is great news.
|
||||
|
||||
|
||||
Features we have recently released:
|
||||
|
||||
- v0.9.25: Added [xAI](https://x.ai/) as a new provider. xAI provides their Grok models for generating content.
|
||||
- v0.9.23: Added support for OpenAI `o` models (`o1`, `o1-mini`, `o3`, etc.); added also an [ERI](https://github.com/MindWorkAI/ERI) server coding assistant as a preview feature behind the RAG feature flag. Your own ERI server can be used to gain access to, e.g., your enterprise data from within AI Studio.
|
||||
- v0.9.22: Added options for preview features; added embedding provider configuration for RAG (preview) and writer mode (experimental preview).
|
||||
- v0.9.18: Added the new Anthropic Heiku model; added Groq and Google Gemini as provider options.
|
||||
- v0.9.17: Added the new Anthropic model `claude-3-5-sonnet-20241022`.
|
||||
- v0.9.16: Added workspace display options & improved the layout of the app window.
|
||||
- v0.9.15: Added the bias-of-the-day assistant. Tells you about a cognitive bias every day.
|
||||
- v0.9.13: You can use `ollama` providers secured with API keys.
|
||||
|
||||
## What is AI Studio?
|
||||
|
||||

|
||||

|
||||
|
||||
MindWork AI Studio is a free desktop app for macOS, Windows, and Linux. It provides a unified user interface for interaction with Large Language Models (LLM). AI Studio also offers so-called assistants, where prompting is not necessary. You can think of AI Studio like an email program: you bring your own API key for the LLM of your choice and can then use these AI systems with AI Studio. Whether you want to use Google Gemini, OpenAI o1, or even your own local AI models.
|
||||
|
||||
**Ready to get started 🤩?** [Download the appropriate setup for your operating system here](documentation/Setup.md).
|
||||
|
||||
**Key advantages:**
|
||||
- **Free of charge**: The app is free to use, both for personal and commercial purposes.
|
||||
- **Independence**: You are not tied to any single provider. Instead, you can choose the provider that best suits their needs. Right now, we support OpenAI (GPT4o etc.), Mistral, Anthropic (Claude), and self-hosted models using [llama.cpp](https://github.com/ggerganov/llama.cpp), [ollama](https://github.com/ollama/ollama), [LM Studio](https://lmstudio.ai/), or [Fireworks](https://fireworks.ai/). Support for Google Gemini, and [Replicate](https://replicate.com/) is planned.
|
||||
- **Independence**: You are not tied to any single provider. Instead, you can choose the provider that best suits their needs. Right now, we support OpenAI (GPT4o, o1, etc.), Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), and self-hosted models using [llama.cpp](https://github.com/ggerganov/llama.cpp), [ollama](https://github.com/ollama/ollama), [LM Studio](https://lmstudio.ai/), [Groq](https://groq.com/), or [Fireworks](https://fireworks.ai/).
|
||||
- **Assistants**: You just want to quickly translate a text? AI Studio has so-called assistants for such and other tasks. No prompting is necessary when working with these assistants.
|
||||
- **Unrestricted usage**: Unlike services like ChatGPT, which impose limits after intensive use, MindWork AI Studio offers unlimited usage through the providers API.
|
||||
- **Cost-effective**: You only pay for what you use, which can be cheaper than monthly subscription services like ChatGPT Plus, especially if used infrequently. But beware, here be dragons: For extremely intensive usage, the API costs can be significantly higher. Unfortunately, providers currently do not offer a way to display current costs in the app. Therefore, check your account with the respective provider to see how your costs are developing. When available, use prepaid and set a cost limit.
|
||||
- **Privacy**: The data entered into the app is not used for training by the providers since we are using the provider's API.
|
||||
- **Privacy**: You can control which providers receive your data using the provider confidence settings. For example, you can set different protection levels for writing emails compared to general chats, etc. Additionally, most providers guarantee that they won't use your data to train new AI systems.
|
||||
- **Flexibility**: Choose the provider and model best suited for your current task.
|
||||
- **No bloatware**: The app requires minimal storage for installation and operates with low memory usage. Additionally, it has a minimal impact on system resources, which is beneficial for battery life.
|
||||
|
||||
@@ -24,16 +76,21 @@ We offer various ways you can support the project:
|
||||
- **One-Time Contributions**: Make a one-time donation and have your name or company logo included in the app as a gesture of our gratitude.
|
||||
|
||||
For companies, sponsoring MindWork AI Studio is not only a way to support innovation but also a valuable opportunity for public relations and marketing. Your company's name and logo will be featured prominently, showcasing your commitment to using cutting-edge AI tools and enhancing your reputation as an innovative enterprise.
|
||||
|
||||
|
||||
To view all available tiers, please visit our [GitHub Sponsors page](https://github.com/sponsors/MindWorkAI).
|
||||
Your support, whether big or small, keeps the wheels turning and is deeply appreciated ❤️.
|
||||
|
||||
## Planned Features
|
||||
Here's an exciting look at some of the features we're planning to add to MindWork AI Studio in future releases:
|
||||
- **More providers**: We plan to add support for additional LLM providers, such as Google Gemini, giving you more options to choose from.
|
||||
- **System prompts**: Integration of a system prompt library will allow you to control the behavior of the LLM with predefined prompts, ensuring consistency and efficiency.
|
||||
- **Text replacement for better privacy**: Define keywords that will be replaced in your chats before sending content to the provider, enhancing your privacy.
|
||||
- **Advanced interactions**: We're full of ideas for advanced interactions tailored for specific use cases, whether in a business context or for writers and other professionals.
|
||||
Here's an exciting look at some of the features we're planning to add to AI Studio in future releases:
|
||||
- **Integrating your data**: You'll be able to integrate your data into AI Studio, like your PDF or Office files, or your Markdown notes.
|
||||
- **Integration of enterprise data:** It will soon be possible to integrate data from the corporate network using a specified interface ([External Retrieval Interface](https://github.com/MindWorkAI/ERI), ERI for short). This will likely require development work by the organization in question.
|
||||
- **Useful assistants:** We'll develop more assistants for everyday tasks.
|
||||
- **Writing mode:** We're integrating a writing mode to help you create extensive works, like comprehensive project proposals, tenders, or your next fantasy novel.
|
||||
- **Specific requirements:** Want an assistant that suits your specific needs? We aim to offer a plugin architecture so organizations and enthusiasts can implement such ideas.
|
||||
- **Voice control:** You'll interact with the AI systems using your voice. To achieve this, we want to integrate voice input (speech-to-text) and output (text-to-speech). However, later on, it should also have a natural conversation flow, i.e., seamless conversation.
|
||||
- **Content creation:** There will be an interface for AI Studio to create content in other apps. You could, for example, create blog posts directly on the target platform or add entries to an internal knowledge management tool. This requires development work by the tool developers.
|
||||
- **Email monitoring:** You can connect your email inboxes with AI Studio. The AI will read your emails and notify you of important events. You'll also be able to access knowledge from your emails in your chats.
|
||||
- **Browser usage:** We're working on offering AI Studio features in your browser via a plugin, allowing, e.g., for spell-checking or text rewriting directly in the browser.
|
||||
|
||||
Stay tuned for more updates and enhancements to make MindWork AI Studio even more powerful and versatile 🤩.
|
||||
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="AI Studio" type="DotNetProject" factoryName=".NET Project">
|
||||
<option name="EXE_PATH" value="$PROJECT_DIR$/MindWork AI Studio/bin/Debug/net8.0/osx-arm64/mindworkAIStudio.dll" />
|
||||
<option name="PROGRAM_PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/MindWork AI Studio" />
|
||||
<option name="PASS_PARENT_ENVS" value="1" />
|
||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||
<option name="USE_MONO" value="0" />
|
||||
<option name="RUNTIME_ARGUMENTS" value="" />
|
||||
<option name="RUNTIME_TYPE" value="coreclr" />
|
||||
<option name="PROJECT_PATH" value="$PROJECT_DIR$/MindWork AI Studio/MindWork AI Studio.csproj" />
|
||||
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
|
||||
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
||||
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||
<option name="PROJECT_TFM" value="net8.0" />
|
||||
<method v="2">
|
||||
<option name="Build" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -1,7 +0,0 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Run Dev" type="CompoundRunConfigurationType">
|
||||
<toRun name="AI Studio" type="DotNetProject" />
|
||||
<toRun name="Tauri Dev" type="ShConfigurationType" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -13,4 +13,6 @@ Global
|
||||
{059FDFCC-7D0B-474E-9F20-B9C437DF1CDD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{059FDFCC-7D0B-474E-9F20-B9C437DF1CDD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -1,6 +1,10 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AI/@EntryIndexedValue">AI</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=EDI/@EntryIndexedValue">EDI</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ERI/@EntryIndexedValue">ERI</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=LLM/@EntryIndexedValue">LLM</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=LM/@EntryIndexedValue">LM</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=MSG/@EntryIndexedValue">MSG</s:String>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=groq/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=ollama/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=tauri_0027s/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
@@ -1 +0,0 @@
|
||||
*.woff2 filter=lfs diff=lfs merge=lfs -text
|
||||
@@ -1,19 +1,18 @@
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Provider;
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Tools;
|
||||
|
||||
// ReSharper disable MemberCanBePrivate.Global
|
||||
|
||||
namespace AIStudio.Agents;
|
||||
|
||||
public abstract class AgentBase(SettingsManager settingsManager, IJSRuntime jsRuntime, ThreadSafeRandom rng) : IAgent
|
||||
public abstract class AgentBase(ILogger<AgentBase> logger, SettingsManager settingsManager, ThreadSafeRandom rng) : IAgent
|
||||
{
|
||||
protected SettingsManager SettingsManager { get; init; } = settingsManager;
|
||||
|
||||
protected IJSRuntime JsRuntime { get; init; } = jsRuntime;
|
||||
|
||||
protected ThreadSafeRandom RNG { get; init; } = rng;
|
||||
|
||||
protected ILogger<AgentBase> Logger { get; init; } = logger;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the type or category of this agent.
|
||||
@@ -104,6 +103,6 @@ public abstract class AgentBase(SettingsManager settingsManager, IJSRuntime jsRu
|
||||
// Use the selected provider to get the AI response.
|
||||
// By awaiting this line, we wait for the entire
|
||||
// content to be streamed.
|
||||
await aiText.CreateFromProviderAsync(providerSettings.CreateProvider(), this.JsRuntime, this.SettingsManager, providerSettings.Model, thread);
|
||||
await aiText.CreateFromProviderAsync(providerSettings.CreateProvider(this.Logger), this.SettingsManager, providerSettings.Model, thread);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Tools;
|
||||
|
||||
namespace AIStudio.Agents;
|
||||
|
||||
public sealed class AgentTextContentCleaner(SettingsManager settingsManager, IJSRuntime jsRuntime, ThreadSafeRandom rng) : AgentBase(settingsManager, jsRuntime, rng)
|
||||
public sealed class AgentTextContentCleaner(ILogger<AgentBase> logger, SettingsManager settingsManager, ThreadSafeRandom rng) : AgentBase(logger, settingsManager, rng)
|
||||
{
|
||||
private static readonly ContentBlock EMPTY_BLOCK = new()
|
||||
{
|
||||
@@ -19,7 +18,7 @@ public sealed class AgentTextContentCleaner(SettingsManager settingsManager, IJS
|
||||
|
||||
#region Overrides of AgentBase
|
||||
|
||||
public override Settings.Provider? ProviderSettings { get; set; }
|
||||
public override AIStudio.Settings.Provider? ProviderSettings { get; set; }
|
||||
|
||||
protected override Type Type => Type.SYSTEM;
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
|
||||
@* ReSharper disable Html.PathError *@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>MindWork AI Studio</title>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<base href="/"/>
|
||||
|
||||
<link rel="icon" type="image/png" href="favicon.png"/>
|
||||
<link href="system/MudBlazor/MudBlazor.min.css" rel="stylesheet" />
|
||||
<link href="system/MudBlazor.Markdown/MudBlazor.Markdown.min.css" rel="stylesheet" />
|
||||
<link href="system/CodeBeam.MudBlazor.Extensions/MudExtensions.min.css" rel="stylesheet" />
|
||||
<link href="app.css" rel="stylesheet" />
|
||||
<HeadOutlet/>
|
||||
<script src="diff.js"></script>
|
||||
</head>
|
||||
|
||||
<body style="overflow: hidden;">
|
||||
<Routes @rendermode="new InteractiveServerRenderMode(prerender: false)"/>
|
||||
<script src="_framework/blazor.web.js" autostart="false"></script>
|
||||
<script src="boot.js"></script>
|
||||
<script src="system/MudBlazor/MudBlazor.min.js"></script>
|
||||
<script src="system/MudBlazor.Markdown/MudBlazor.Markdown.min.js"></script>
|
||||
<script src="system/CodeBeam.MudBlazor.Extensions/MudExtensions.min.js"></script>
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
+2
-7
@@ -1,5 +1,4 @@
|
||||
@attribute [Route(Path.ASSISTANT_AGENDA)]
|
||||
@using AIStudio.Tools
|
||||
@attribute [Route(Routes.ASSISTANT_AGENDA)]
|
||||
@inherits AssistantBaseCore
|
||||
|
||||
<MudTextField T="string" @bind-Text="@this.inputName" Validation="@this.ValidateName" Label="Meeting Name" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Tag" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES" HelperText="Name the meeting, seminar, etc." Placeholder="Weekly jour fixe" Class="mb-3"/>
|
||||
@@ -49,8 +48,4 @@
|
||||
}
|
||||
|
||||
<EnumSelection T="CommonLanguages" NameFunc="@(language => language.NameSelecting())" @bind-Value="@this.selectedTargetLanguage" ValidateSelection="@this.ValidateTargetLanguage" Icon="@Icons.Material.Filled.Translate" Label="Target language" AllowOther="@true" OtherValue="CommonLanguages.OTHER" @bind-OtherInput="@this.customTargetLanguage" ValidateOther="@this.ValidateCustomLanguage" LabelOther="Custom target language" />
|
||||
<ProviderSelection @bind-ProviderSettings="@this.providerSettings" ValidateProvider="@this.ValidatingProvider"/>
|
||||
|
||||
<MudButton Variant="Variant.Filled" Class="mb-3" OnClick="() => this.CreateAgenda()">
|
||||
Create agenda
|
||||
</MudButton>
|
||||
<ProviderSelection @bind-ProviderSettings="@this.providerSettings" ValidateProvider="@this.ValidatingProvider"/>
|
||||
+13
-13
@@ -1,12 +1,13 @@
|
||||
using System.Text;
|
||||
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Tools;
|
||||
|
||||
namespace AIStudio.Components.Pages.Agenda;
|
||||
namespace AIStudio.Assistants.Agenda;
|
||||
|
||||
public partial class AssistantAgenda : AssistantBaseCore
|
||||
{
|
||||
public override Tools.Components Component => Tools.Components.AGENDA_ASSISTANT;
|
||||
|
||||
protected override string Title => "Agenda Planner";
|
||||
|
||||
protected override string Description =>
|
||||
@@ -94,20 +95,18 @@ public partial class AssistantAgenda : AssistantBaseCore
|
||||
- Mary Jane: Work package 3
|
||||
""";
|
||||
|
||||
protected override IReadOnlyList<IButtonData> FooterButtons =>
|
||||
[
|
||||
new SendToButton
|
||||
{
|
||||
Self = SendTo.AGENDA_ASSISTANT,
|
||||
},
|
||||
];
|
||||
protected override IReadOnlyList<IButtonData> FooterButtons => [];
|
||||
|
||||
protected override string SubmitText => "Create Agenda";
|
||||
|
||||
protected override Func<Task> SubmitAction => this.CreateAgenda;
|
||||
|
||||
protected override ChatThread ConvertToChatThread => (this.chatThread ?? new()) with
|
||||
{
|
||||
SystemPrompt = SystemPrompts.DEFAULT,
|
||||
};
|
||||
|
||||
protected override void ResetFrom()
|
||||
protected override void ResetForm()
|
||||
{
|
||||
this.inputContent = string.Empty;
|
||||
this.contentLines.Clear();
|
||||
@@ -159,7 +158,6 @@ public partial class AssistantAgenda : AssistantBaseCore
|
||||
this.durationBreaks = this.SettingsManager.ConfigurationData.Agenda.PreselectBreakTime;
|
||||
this.activeParticipation = this.SettingsManager.ConfigurationData.Agenda.PreselectActiveParticipation;
|
||||
this.numberParticipants = this.SettingsManager.ConfigurationData.Agenda.PreselectNumberParticipants;
|
||||
this.providerSettings = this.SettingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == this.SettingsManager.ConfigurationData.Agenda.PreselectedProvider);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -195,7 +193,6 @@ public partial class AssistantAgenda : AssistantBaseCore
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
this.MightPreselectValues();
|
||||
var deferredContent = MessageBus.INSTANCE.CheckDeferredMessages<string>(Event.SEND_TO_AGENDA_ASSISTANT).FirstOrDefault();
|
||||
if (deferredContent is not null)
|
||||
this.inputContent = deferredContent;
|
||||
@@ -303,7 +300,7 @@ public partial class AssistantAgenda : AssistantBaseCore
|
||||
if(string.IsNullOrWhiteSpace(content))
|
||||
return "Please provide some content for the agenda. What are the main points of the meeting or the seminar?";
|
||||
|
||||
var lines = content.Split('\n');
|
||||
var lines = content.Split('\n', StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
if(!line.TrimStart().StartsWith('-'))
|
||||
return "Please start each line of your content list with a dash (-) to create a bullet point list.";
|
||||
@@ -363,6 +360,9 @@ public partial class AssistantAgenda : AssistantBaseCore
|
||||
|
||||
private string PromptLanguage()
|
||||
{
|
||||
if(this.selectedTargetLanguage is CommonLanguages.AS_IS)
|
||||
return "Use the same language as the input.";
|
||||
|
||||
if(this.selectedTargetLanguage is CommonLanguages.OTHER)
|
||||
return this.customTargetLanguage;
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace AIStudio.Components.Pages.Agenda;
|
||||
namespace AIStudio.Assistants.Agenda;
|
||||
|
||||
public enum NumberParticipants
|
||||
{
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace AIStudio.Components.Pages.Agenda;
|
||||
namespace AIStudio.Assistants.Agenda;
|
||||
|
||||
public static class NumberParticipantsExtensions
|
||||
{
|
||||
@@ -0,0 +1,134 @@
|
||||
@using AIStudio.Chat
|
||||
|
||||
<div class="inner-scrolling-context">
|
||||
|
||||
<MudText Typo="Typo.h3" Class="mb-2 mr-3">
|
||||
@(this.Title)
|
||||
</MudText>
|
||||
|
||||
<InnerScrolling>
|
||||
<ChildContent>
|
||||
<MudForm @ref="@(this.form)" @bind-IsValid="@(this.inputIsValid)" @bind-Errors="@(this.inputIssues)" FieldChanged="@this.TriggerFormChange" Class="pr-2">
|
||||
<MudText Typo="Typo.body1" Align="Align.Justify" Class="mb-6">
|
||||
@this.Description
|
||||
</MudText>
|
||||
|
||||
@if (this.Body is not null)
|
||||
{
|
||||
<CascadingValue Value="@this">
|
||||
@this.Body
|
||||
</CascadingValue>
|
||||
|
||||
<MudButton Disabled="@this.SubmitDisabled" Variant="Variant.Filled" Class="mb-3" OnClick="() => this.SubmitAction()" Style="@this.SubmitButtonStyle">
|
||||
@this.SubmitText
|
||||
</MudButton>
|
||||
}
|
||||
</MudForm>
|
||||
<Issues IssuesData="@(this.inputIssues)"/>
|
||||
|
||||
@if (this.ShowDedicatedProgress && this.isProcessing)
|
||||
{
|
||||
<MudProgressLinear Color="Color.Primary" Indeterminate="true" Class="mb-6" />
|
||||
}
|
||||
|
||||
<div id="@RESULT_DIV_ID" class="mr-2 mt-3">
|
||||
</div>
|
||||
|
||||
<div id="@BEFORE_RESULT_DIV_ID" class="mt-3">
|
||||
</div>
|
||||
|
||||
@if (this.ShowResult && !this.ShowEntireChatThread && this.resultingContentBlock is not null)
|
||||
{
|
||||
<ContentBlockComponent Role="@(this.resultingContentBlock.Role)" Type="@(this.resultingContentBlock.ContentType)" Time="@(this.resultingContentBlock.Time)" Content="@(this.resultingContentBlock.Content)"/>
|
||||
}
|
||||
|
||||
@if(this.ShowResult && this.ShowEntireChatThread && this.chatThread is not null)
|
||||
{
|
||||
foreach (var block in this.chatThread.Blocks.OrderBy(n => n.Time))
|
||||
{
|
||||
@if (!block.HideFromUser)
|
||||
{
|
||||
<ContentBlockComponent Role="@block.Role" Type="@block.ContentType" Time="@block.Time" Content="@block.Content"/>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<div id="@AFTER_RESULT_DIV_ID" class="mt-3">
|
||||
</div>
|
||||
</ChildContent>
|
||||
<FooterContent>
|
||||
<MudStack Row="@true" Wrap="Wrap.Wrap" Class="ma-1">
|
||||
|
||||
@if (!this.FooterButtons.Any(x => x.Type is ButtonTypes.SEND_TO))
|
||||
{
|
||||
@if (this.ShowSendTo)
|
||||
{
|
||||
<MudMenu StartIcon="@Icons.Material.Filled.Apps" EndIcon="@Icons.Material.Filled.KeyboardArrowDown" Label="Send to ..." Variant="Variant.Filled" Style="@this.GetSendToColor()" Class="rounded">
|
||||
@foreach (var assistant in Enum.GetValues<Components>().Where(n => n.AllowSendTo()).OrderBy(n => n.Name().Length))
|
||||
{
|
||||
<MudMenuItem OnClick="() => this.SendToAssistant(assistant, new())">
|
||||
@assistant.Name()
|
||||
</MudMenuItem>
|
||||
}
|
||||
</MudMenu>
|
||||
}
|
||||
}
|
||||
|
||||
@foreach (var button in this.FooterButtons)
|
||||
{
|
||||
switch (button)
|
||||
{
|
||||
case ButtonData buttonData when !string.IsNullOrWhiteSpace(buttonData.Tooltip):
|
||||
<MudTooltip Text="@buttonData.Tooltip">
|
||||
<MudButton Variant="Variant.Filled" Color="@buttonData.Color" StartIcon="@GetButtonIcon(buttonData.Icon)" OnClick="async () => await buttonData.AsyncAction()">
|
||||
@buttonData.Text
|
||||
</MudButton>
|
||||
</MudTooltip>
|
||||
break;
|
||||
|
||||
case ButtonData buttonData:
|
||||
<MudButton Variant="Variant.Filled" Color="@buttonData.Color" StartIcon="@GetButtonIcon(buttonData.Icon)" OnClick="async () => await buttonData.AsyncAction()">
|
||||
@buttonData.Text
|
||||
</MudButton>
|
||||
break;
|
||||
|
||||
case SendToButton sendToButton:
|
||||
<MudMenu StartIcon="@Icons.Material.Filled.Apps" EndIcon="@Icons.Material.Filled.KeyboardArrowDown" Label="Send to ..." Variant="Variant.Filled" Style="@this.GetSendToColor()" Class="rounded">
|
||||
@foreach (var assistant in Enum.GetValues<Components>().Where(n => n.AllowSendTo()).OrderBy(n => n.Name().Length))
|
||||
{
|
||||
<MudMenuItem OnClick="() => this.SendToAssistant(assistant, sendToButton)">
|
||||
@assistant.Name()
|
||||
</MudMenuItem>
|
||||
}
|
||||
</MudMenu>
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@if (this.ShowCopyResult)
|
||||
{
|
||||
<MudButton Variant="Variant.Filled" StartIcon="@Icons.Material.Filled.ContentCopy" OnClick="() => this.CopyToClipboard()">
|
||||
Copy result
|
||||
</MudButton>
|
||||
}
|
||||
|
||||
@if (this.ShowReset)
|
||||
{
|
||||
<MudButton Variant="Variant.Filled" Style="@this.GetResetColor()" StartIcon="@Icons.Material.Filled.Refresh" OnClick="() => this.InnerResetForm()">
|
||||
Reset
|
||||
</MudButton>
|
||||
}
|
||||
|
||||
@if (this.SettingsManager.ConfigurationData.LLMProviders.ShowProviderConfidence)
|
||||
{
|
||||
<ConfidenceInfo Mode="ConfidenceInfoMode.BUTTON" LLMProvider="@this.providerSettings.UsedLLMProvider"/>
|
||||
}
|
||||
|
||||
@if (this.AllowProfiles && this.ShowProfileSelection)
|
||||
{
|
||||
<ProfileSelection MarginLeft="" @bind-CurrentProfile="@this.currentProfile"/>
|
||||
}
|
||||
</MudStack>
|
||||
</FooterContent>
|
||||
</InnerScrolling>
|
||||
</div>
|
||||
@@ -0,0 +1,385 @@
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Provider;
|
||||
using AIStudio.Settings;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
using MudBlazor.Utilities;
|
||||
|
||||
using RustService = AIStudio.Tools.RustService;
|
||||
using Timer = System.Timers.Timer;
|
||||
|
||||
namespace AIStudio.Assistants;
|
||||
|
||||
public abstract partial class AssistantBase : ComponentBase, IMessageBusReceiver, IDisposable
|
||||
{
|
||||
[Inject]
|
||||
protected SettingsManager SettingsManager { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
protected IJSRuntime JsRuntime { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
protected ThreadSafeRandom RNG { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
protected ISnackbar Snackbar { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
protected RustService RustService { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
protected NavigationManager NavigationManager { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
protected ILogger<AssistantBase> Logger { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private MudTheme ColorTheme { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private MessageBus MessageBus { get; init; } = null!;
|
||||
|
||||
internal const string RESULT_DIV_ID = "assistantResult";
|
||||
internal const string BEFORE_RESULT_DIV_ID = "beforeAssistantResult";
|
||||
internal const string AFTER_RESULT_DIV_ID = "afterAssistantResult";
|
||||
|
||||
protected abstract string Title { get; }
|
||||
|
||||
protected abstract string Description { get; }
|
||||
|
||||
protected abstract string SystemPrompt { get; }
|
||||
|
||||
public abstract Tools.Components Component { get; }
|
||||
|
||||
protected virtual Func<string> Result2Copy => () => this.resultingContentBlock is null ? string.Empty : this.resultingContentBlock.Content switch
|
||||
{
|
||||
ContentText textBlock => textBlock.Text,
|
||||
_ => string.Empty,
|
||||
};
|
||||
|
||||
protected abstract void ResetForm();
|
||||
|
||||
protected abstract bool MightPreselectValues();
|
||||
|
||||
protected abstract string SubmitText { get; }
|
||||
|
||||
protected abstract Func<Task> SubmitAction { get; }
|
||||
|
||||
protected virtual bool SubmitDisabled => false;
|
||||
|
||||
private protected virtual RenderFragment? Body => null;
|
||||
|
||||
protected virtual bool ShowResult => true;
|
||||
|
||||
protected virtual bool ShowEntireChatThread => false;
|
||||
|
||||
protected virtual bool AllowProfiles => true;
|
||||
|
||||
protected virtual bool ShowProfileSelection => true;
|
||||
|
||||
protected virtual bool ShowDedicatedProgress => false;
|
||||
|
||||
protected virtual bool ShowSendTo => true;
|
||||
|
||||
protected virtual bool ShowCopyResult => true;
|
||||
|
||||
protected virtual bool ShowReset => true;
|
||||
|
||||
protected virtual ChatThread ConvertToChatThread => this.chatThread ?? new();
|
||||
|
||||
protected virtual IReadOnlyList<IButtonData> FooterButtons => [];
|
||||
|
||||
protected static readonly Dictionary<string, object?> USER_INPUT_ATTRIBUTES = new();
|
||||
|
||||
protected AIStudio.Settings.Provider providerSettings;
|
||||
protected MudForm? form;
|
||||
protected bool inputIsValid;
|
||||
protected Profile currentProfile = Profile.NO_PROFILE;
|
||||
protected ChatThread? chatThread;
|
||||
|
||||
private readonly Timer formChangeTimer = new(TimeSpan.FromSeconds(1.6));
|
||||
|
||||
private ContentBlock? resultingContentBlock;
|
||||
private string[] inputIssues = [];
|
||||
private bool isProcessing;
|
||||
|
||||
#region Overrides of ComponentBase
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
this.formChangeTimer.AutoReset = false;
|
||||
this.formChangeTimer.Elapsed += async (_, _) =>
|
||||
{
|
||||
this.formChangeTimer.Stop();
|
||||
await this.OnFormChange();
|
||||
};
|
||||
|
||||
this.MightPreselectValues();
|
||||
this.providerSettings = this.SettingsManager.GetPreselectedProvider(this.Component);
|
||||
this.currentProfile = this.SettingsManager.GetPreselectedProfile(this.Component);
|
||||
|
||||
this.MessageBus.RegisterComponent(this);
|
||||
this.MessageBus.ApplyFilters(this, [], [ Event.COLOR_THEME_CHANGED ]);
|
||||
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
// Configure the spellchecking for the user input:
|
||||
this.SettingsManager.InjectSpellchecking(USER_INPUT_ATTRIBUTES);
|
||||
|
||||
await base.OnParametersSetAsync();
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
// Reset the validation when not editing and on the first render.
|
||||
// We don't want to show validation errors when the user opens the dialog.
|
||||
if(firstRender)
|
||||
this.form?.ResetValidation();
|
||||
|
||||
await base.OnAfterRenderAsync(firstRender);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation of IMessageBusReceiver
|
||||
|
||||
public string ComponentName => nameof(AssistantBase);
|
||||
|
||||
public Task ProcessMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data)
|
||||
{
|
||||
switch (triggeredEvent)
|
||||
{
|
||||
case Event.COLOR_THEME_CHANGED:
|
||||
this.StateHasChanged();
|
||||
break;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<TResult?> ProcessMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data)
|
||||
{
|
||||
return Task.FromResult<TResult?>(default);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private string SubmitButtonStyle => this.SettingsManager.ConfigurationData.LLMProviders.ShowProviderConfidence ? this.providerSettings.UsedLLMProvider.GetConfidence(this.SettingsManager).StyleBorder(this.SettingsManager) : string.Empty;
|
||||
|
||||
protected string? ValidatingProvider(AIStudio.Settings.Provider provider)
|
||||
{
|
||||
if(provider.UsedLLMProvider == LLMProviders.NONE)
|
||||
return "Please select a provider.";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void TriggerFormChange(FormFieldChangedEventArgs _)
|
||||
{
|
||||
this.formChangeTimer.Stop();
|
||||
this.formChangeTimer.Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method is called after any form field has changed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method is called after a delay of 1.6 seconds. This is to prevent
|
||||
/// the method from being called too often. This method is called after
|
||||
/// the user has stopped typing or selecting options.
|
||||
/// </remarks>
|
||||
protected virtual Task OnFormChange() => Task.CompletedTask;
|
||||
|
||||
/// <summary>
|
||||
/// Add an issue to the UI.
|
||||
/// </summary>
|
||||
/// <param name="issue">The issue to add.</param>
|
||||
protected void AddInputIssue(string issue)
|
||||
{
|
||||
Array.Resize(ref this.inputIssues, this.inputIssues.Length + 1);
|
||||
this.inputIssues[^1] = issue;
|
||||
this.inputIsValid = false;
|
||||
this.StateHasChanged();
|
||||
}
|
||||
|
||||
protected void CreateChatThread()
|
||||
{
|
||||
this.chatThread = new()
|
||||
{
|
||||
SelectedProvider = this.providerSettings.Id,
|
||||
SelectedProfile = this.AllowProfiles ? this.currentProfile.Id : Profile.NO_PROFILE.Id,
|
||||
SystemPrompt = this.SystemPrompt,
|
||||
WorkspaceId = Guid.Empty,
|
||||
ChatId = Guid.NewGuid(),
|
||||
Name = $"Assistant - {this.Title}",
|
||||
Seed = this.RNG.Next(),
|
||||
Blocks = [],
|
||||
};
|
||||
}
|
||||
|
||||
protected Guid CreateChatThread(Guid workspaceId, string name)
|
||||
{
|
||||
var chatId = Guid.NewGuid();
|
||||
this.chatThread = new()
|
||||
{
|
||||
SelectedProvider = this.providerSettings.Id,
|
||||
SelectedProfile = this.AllowProfiles ? this.currentProfile.Id : Profile.NO_PROFILE.Id,
|
||||
SystemPrompt = this.SystemPrompt,
|
||||
WorkspaceId = workspaceId,
|
||||
ChatId = chatId,
|
||||
Name = name,
|
||||
Seed = this.RNG.Next(),
|
||||
Blocks = [],
|
||||
};
|
||||
|
||||
return chatId;
|
||||
}
|
||||
|
||||
protected DateTimeOffset AddUserRequest(string request, bool hideContentFromUser = false)
|
||||
{
|
||||
var time = DateTimeOffset.Now;
|
||||
this.chatThread!.Blocks.Add(new ContentBlock
|
||||
{
|
||||
Time = time,
|
||||
ContentType = ContentType.TEXT,
|
||||
HideFromUser = hideContentFromUser,
|
||||
Role = ChatRole.USER,
|
||||
Content = new ContentText
|
||||
{
|
||||
Text = request,
|
||||
},
|
||||
});
|
||||
|
||||
return time;
|
||||
}
|
||||
|
||||
protected async Task<string> AddAIResponseAsync(DateTimeOffset time, bool hideContentFromUser = false)
|
||||
{
|
||||
var aiText = new ContentText
|
||||
{
|
||||
// We have to wait for the remote
|
||||
// for the content stream:
|
||||
InitialRemoteWait = true,
|
||||
};
|
||||
|
||||
this.resultingContentBlock = new ContentBlock
|
||||
{
|
||||
Time = time,
|
||||
ContentType = ContentType.TEXT,
|
||||
Role = ChatRole.AI,
|
||||
Content = aiText,
|
||||
HideFromUser = hideContentFromUser,
|
||||
};
|
||||
|
||||
if (this.chatThread is not null)
|
||||
{
|
||||
this.chatThread.Blocks.Add(this.resultingContentBlock);
|
||||
this.chatThread.SelectedProvider = this.providerSettings.Id;
|
||||
}
|
||||
|
||||
this.isProcessing = true;
|
||||
this.StateHasChanged();
|
||||
|
||||
// Use the selected provider to get the AI response.
|
||||
// By awaiting this line, we wait for the entire
|
||||
// content to be streamed.
|
||||
await aiText.CreateFromProviderAsync(this.providerSettings.CreateProvider(this.Logger), this.SettingsManager, this.providerSettings.Model, this.chatThread);
|
||||
|
||||
this.isProcessing = false;
|
||||
this.StateHasChanged();
|
||||
|
||||
// Return the AI response:
|
||||
return aiText.Text;
|
||||
}
|
||||
|
||||
protected async Task CopyToClipboard()
|
||||
{
|
||||
await this.RustService.CopyText2Clipboard(this.Snackbar, this.Result2Copy());
|
||||
}
|
||||
|
||||
private static string? GetButtonIcon(string icon)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(icon))
|
||||
return null;
|
||||
|
||||
return icon;
|
||||
}
|
||||
|
||||
protected Task SendToAssistant(Tools.Components destination, SendToButton sendToButton)
|
||||
{
|
||||
if (!destination.AllowSendTo())
|
||||
return Task.CompletedTask;
|
||||
|
||||
var contentToSend = sendToButton == default ? string.Empty : sendToButton.UseResultingContentBlockData switch
|
||||
{
|
||||
false => sendToButton.GetText(),
|
||||
true => this.resultingContentBlock?.Content switch
|
||||
{
|
||||
ContentText textBlock => textBlock.Text,
|
||||
_ => string.Empty,
|
||||
},
|
||||
};
|
||||
|
||||
var sendToData = destination.GetData();
|
||||
switch (destination)
|
||||
{
|
||||
case Tools.Components.CHAT:
|
||||
var convertedChatThread = this.ConvertToChatThread;
|
||||
convertedChatThread = convertedChatThread with { SelectedProvider = this.providerSettings.Id };
|
||||
MessageBus.INSTANCE.DeferMessage(this, sendToData.Event, convertedChatThread);
|
||||
break;
|
||||
|
||||
default:
|
||||
MessageBus.INSTANCE.DeferMessage(this, sendToData.Event, contentToSend);
|
||||
break;
|
||||
}
|
||||
|
||||
this.NavigationManager.NavigateTo(sendToData.Route);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task InnerResetForm()
|
||||
{
|
||||
this.resultingContentBlock = null;
|
||||
this.providerSettings = default;
|
||||
|
||||
await this.JsRuntime.ClearDiv(RESULT_DIV_ID);
|
||||
await this.JsRuntime.ClearDiv(AFTER_RESULT_DIV_ID);
|
||||
|
||||
this.ResetForm();
|
||||
this.providerSettings = this.SettingsManager.GetPreselectedProvider(this.Component);
|
||||
|
||||
this.inputIsValid = false;
|
||||
this.inputIssues = [];
|
||||
|
||||
this.form?.ResetValidation();
|
||||
this.StateHasChanged();
|
||||
this.form?.ResetValidation();
|
||||
}
|
||||
|
||||
private string GetResetColor() => this.SettingsManager.IsDarkMode switch
|
||||
{
|
||||
true => $"background-color: #804000",
|
||||
false => $"background-color: {this.ColorTheme.GetCurrentPalette(this.SettingsManager).Warning.Value}",
|
||||
};
|
||||
|
||||
private string GetSendToColor() => this.SettingsManager.IsDarkMode switch
|
||||
{
|
||||
true => $"background-color: #004080",
|
||||
false => $"background-color: {this.ColorTheme.GetCurrentPalette(this.SettingsManager).InfoLighten}",
|
||||
};
|
||||
|
||||
#region Implementation of IDisposable
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this.MessageBus.Unregister(this);
|
||||
this.formChangeTimer.Dispose();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Rendering;
|
||||
|
||||
namespace AIStudio.Components;
|
||||
namespace AIStudio.Assistants;
|
||||
|
||||
//
|
||||
// See https://stackoverflow.com/a/77300384/2258393 for why this class is necessary
|
||||
@@ -0,0 +1,14 @@
|
||||
@attribute [Route(Routes.ASSISTANT_BIAS)]
|
||||
@inherits AssistantBaseCore
|
||||
|
||||
<MudText Typo="Typo.body1">
|
||||
<b>Links:</b>
|
||||
</MudText>
|
||||
<MudList T="string" Class="mb-6">
|
||||
<MudListItem T="string" Icon="@Icons.Material.Filled.Link" Target="_blank" Href="https://en.wikipedia.org/wiki/List_of_cognitive_biases">Wikipedia list of cognitive biases</MudListItem>
|
||||
<MudListItem T="string" Icon="@Icons.Material.Filled.Link" Target="_blank" Href="https://commons.wikimedia.org/wiki/File:Cognitive_Bias_Codex_With_Definitions_1-2,_an_Extension_of_the_work_of_John_Manoogian_by_Brian_Rene_Morrissette.png">Extended bias poster</MudListItem>
|
||||
<MudListItem T="string" Icon="@Icons.Material.Filled.Link" Target="_blank" Href="https://betterhumans.pub/cognitive-bias-cheat-sheet-55a472476b18">Blog post of Buster Benson: "Cognitive bias cheat sheet"</MudListItem>
|
||||
</MudList>
|
||||
|
||||
<EnumSelection T="CommonLanguages" NameFunc="@(language => language.NameSelecting())" @bind-Value="@this.selectedTargetLanguage" ValidateSelection="@this.ValidateTargetLanguage" Icon="@Icons.Material.Filled.Translate" Label="Target language" AllowOther="@true" OtherValue="CommonLanguages.OTHER" @bind-OtherInput="@this.customTargetLanguage" ValidateOther="@this.ValidateCustomLanguage" LabelOther="Custom target language" />
|
||||
<ProviderSelection @bind-ProviderSettings="@this.providerSettings" ValidateProvider="@this.ValidatingProvider"/>
|
||||
@@ -0,0 +1,164 @@
|
||||
using System.Text;
|
||||
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Settings.DataModel;
|
||||
|
||||
namespace AIStudio.Assistants.BiasDay;
|
||||
|
||||
public partial class BiasOfTheDayAssistant : AssistantBaseCore
|
||||
{
|
||||
public override Tools.Components Component => Tools.Components.BIAS_DAY_ASSISTANT;
|
||||
|
||||
protected override string Title => "Bias of the Day";
|
||||
|
||||
protected override string Description =>
|
||||
"""
|
||||
Learn about a different cognitive bias every day. You can also ask the LLM your questions. The idea behind
|
||||
"Bias of the Day" is based on work by Buster Benson, John Manoogian III, and Brian Rene Morrissette. Buster
|
||||
Benson grouped the biases, and the original texts come from Wikipedia. Brian Rene Morrissette condensed them
|
||||
into a shorter version. Finally, John Manoogian III created the original poster based on Benson's work and
|
||||
Morrissette's texts. Thorsten Sommer compared all texts for integration into AI Studio with the current Wikipedia
|
||||
versions, updated them, and added source references. The idea of learning about one bias each day based on John's
|
||||
poster comes from Drew Nelson.
|
||||
""";
|
||||
|
||||
protected override string SystemPrompt => $"""
|
||||
You are a friendly, helpful expert on cognitive bias. You studied psychology and
|
||||
have a lot of experience. You explain a bias every day. Today's bias belongs to
|
||||
the category: "{this.biasOfTheDay.Category.ToName()}". We have the following
|
||||
thoughts on this category:
|
||||
|
||||
{this.biasOfTheDay.Category.GetThoughts()}
|
||||
|
||||
Today's bias is:
|
||||
{this.biasOfTheDay.Description}
|
||||
{this.SystemPromptSources()}
|
||||
Important: you use the following language: {this.SystemPromptLanguage()}. Please
|
||||
ask the user a personal question at the end to encourage them to think about
|
||||
this bias.
|
||||
""";
|
||||
|
||||
protected override IReadOnlyList<IButtonData> FooterButtons => [];
|
||||
|
||||
protected override string SubmitText => "Show me the bias of the day";
|
||||
|
||||
protected override Func<Task> SubmitAction => this.TellBias;
|
||||
|
||||
protected override bool ShowSendTo => false;
|
||||
|
||||
protected override bool ShowCopyResult => false;
|
||||
|
||||
protected override bool ShowReset => false;
|
||||
|
||||
protected override void ResetForm()
|
||||
{
|
||||
if (!this.MightPreselectValues())
|
||||
{
|
||||
this.selectedTargetLanguage = CommonLanguages.AS_IS;
|
||||
this.customTargetLanguage = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool MightPreselectValues()
|
||||
{
|
||||
if (this.SettingsManager.ConfigurationData.BiasOfTheDay.PreselectOptions)
|
||||
{
|
||||
this.selectedTargetLanguage = this.SettingsManager.ConfigurationData.BiasOfTheDay.PreselectedTargetLanguage;
|
||||
this.customTargetLanguage = this.SettingsManager.ConfigurationData.BiasOfTheDay.PreselectedOtherLanguage;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private Bias biasOfTheDay = BiasCatalog.NONE;
|
||||
private CommonLanguages selectedTargetLanguage = CommonLanguages.AS_IS;
|
||||
private string customTargetLanguage = string.Empty;
|
||||
|
||||
private string? ValidateTargetLanguage(CommonLanguages language)
|
||||
{
|
||||
if(language is CommonLanguages.AS_IS)
|
||||
return "Please select a target language for the bias.";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string? ValidateCustomLanguage(string language)
|
||||
{
|
||||
if(this.selectedTargetLanguage == CommonLanguages.OTHER && string.IsNullOrWhiteSpace(language))
|
||||
return "Please provide a custom language.";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string SystemPromptSources()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
if (this.biasOfTheDay.Links.Count > 0)
|
||||
{
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("Please share the following sources with the user as a Markdown list:");
|
||||
foreach (var link in this.biasOfTheDay.Links)
|
||||
sb.AppendLine($"- {link}");
|
||||
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private string SystemPromptLanguage()
|
||||
{
|
||||
if(this.selectedTargetLanguage is CommonLanguages.OTHER)
|
||||
return this.customTargetLanguage;
|
||||
|
||||
return this.selectedTargetLanguage.Name();
|
||||
}
|
||||
|
||||
private async Task TellBias()
|
||||
{
|
||||
bool useDrawnBias = false;
|
||||
if(this.SettingsManager.ConfigurationData.BiasOfTheDay.RestrictOneBiasPerDay)
|
||||
{
|
||||
if(this.SettingsManager.ConfigurationData.BiasOfTheDay.DateLastBiasDrawn == DateOnly.FromDateTime(DateTime.Now))
|
||||
{
|
||||
var biasChat = new LoadChat
|
||||
{
|
||||
WorkspaceId = KnownWorkspaces.BIAS_WORKSPACE_ID,
|
||||
ChatId = this.SettingsManager.ConfigurationData.BiasOfTheDay.BiasOfTheDayChatId,
|
||||
};
|
||||
|
||||
if (WorkspaceBehaviour.IsChatExisting(biasChat))
|
||||
{
|
||||
MessageBus.INSTANCE.DeferMessage(this, Event.LOAD_CHAT, biasChat);
|
||||
this.NavigationManager.NavigateTo(Routes.CHAT);
|
||||
return;
|
||||
}
|
||||
else
|
||||
useDrawnBias = true;
|
||||
}
|
||||
}
|
||||
|
||||
await this.form!.Validate();
|
||||
if (!this.inputIsValid)
|
||||
return;
|
||||
|
||||
this.biasOfTheDay = useDrawnBias ?
|
||||
BiasCatalog.ALL_BIAS[this.SettingsManager.ConfigurationData.BiasOfTheDay.BiasOfTheDayId] :
|
||||
BiasCatalog.GetRandomBias(this.SettingsManager.ConfigurationData.BiasOfTheDay.UsedBias);
|
||||
|
||||
var chatId = this.CreateChatThread(KnownWorkspaces.BIAS_WORKSPACE_ID, this.biasOfTheDay.Name);
|
||||
this.SettingsManager.ConfigurationData.BiasOfTheDay.BiasOfTheDayId = this.biasOfTheDay.Id;
|
||||
this.SettingsManager.ConfigurationData.BiasOfTheDay.BiasOfTheDayChatId = chatId;
|
||||
this.SettingsManager.ConfigurationData.BiasOfTheDay.DateLastBiasDrawn = DateOnly.FromDateTime(DateTime.Now);
|
||||
await this.SettingsManager.StoreSettings();
|
||||
var time = this.AddUserRequest(
|
||||
"""
|
||||
Please tell me about the bias of the day.
|
||||
""", true);
|
||||
|
||||
// Start the AI response without waiting for it to finish:
|
||||
_ = this.AddAIResponseAsync(time);
|
||||
await this.SendToAssistant(Tools.Components.CHAT, default);
|
||||
}
|
||||
}
|
||||
+4
-7
@@ -1,11 +1,12 @@
|
||||
@attribute [Route(Path.ASSISTANT_CODING)]
|
||||
@attribute [Route(Routes.ASSISTANT_CODING)]
|
||||
@inherits AssistantBaseCore
|
||||
|
||||
<MudExpansionPanels Class="mb-3">
|
||||
@for (var contextIndex = 0; contextIndex < this.codingContexts.Count; contextIndex++)
|
||||
{
|
||||
var codingContext = this.codingContexts[contextIndex];
|
||||
<ExpansionPanel HeaderText="@codingContext.Id" HeaderIcon="@Icons.Material.Filled.Code">
|
||||
var index = contextIndex;
|
||||
<ExpansionPanel HeaderText="@codingContext.Id" HeaderIcon="@Icons.Material.Filled.Code" ShowEndButton="@true" EndButtonColor="Color.Error" EndButtonIcon="@Icons.Material.Filled.Delete" EndButtonTooltip="Delete context" EndButtonClickAsync="@(() => this.DeleteContext(index))">
|
||||
<CodingContextItem @bind-CodingContext="@codingContext"/>
|
||||
</ExpansionPanel>
|
||||
}
|
||||
@@ -23,8 +24,4 @@
|
||||
</MudStack>
|
||||
|
||||
<MudTextField T="string" @bind-Text="@this.questions" Validation="@this.ValidateQuestions" AdornmentIcon="@Icons.Material.Filled.QuestionMark" Adornment="Adornment.Start" Label="Your question(s)" Variant="Variant.Outlined" Lines="6" AutoGrow="@true" MaxLines="12" Class="mb-3" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
|
||||
<ProviderSelection @bind-ProviderSettings="@this.providerSettings" ValidateProvider="@this.ValidatingProvider"/>
|
||||
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Info" OnClick="() => this.GetSupport()" Class="mb-3">
|
||||
Get support
|
||||
</MudButton>
|
||||
<ProviderSelection @bind-ProviderSettings="@this.providerSettings" ValidateProvider="@this.ValidatingProvider"/>
|
||||
+21
-13
@@ -1,11 +1,11 @@
|
||||
using System.Text;
|
||||
|
||||
using AIStudio.Tools;
|
||||
|
||||
namespace AIStudio.Components.Pages.Coding;
|
||||
namespace AIStudio.Assistants.Coding;
|
||||
|
||||
public partial class AssistantCoding : AssistantBaseCore
|
||||
{
|
||||
public override Tools.Components Component => Tools.Components.CODING_ASSISTANT;
|
||||
|
||||
protected override string Title => "Coding Assistant";
|
||||
|
||||
protected override string Description =>
|
||||
@@ -26,15 +26,13 @@ public partial class AssistantCoding : AssistantBaseCore
|
||||
When the user asks in a different language than English, you answer in the same language!
|
||||
""";
|
||||
|
||||
protected override IReadOnlyList<IButtonData> FooterButtons =>
|
||||
[
|
||||
new SendToButton
|
||||
{
|
||||
Self = SendTo.CODING_ASSISTANT,
|
||||
},
|
||||
];
|
||||
protected override IReadOnlyList<IButtonData> FooterButtons => [];
|
||||
|
||||
protected override void ResetFrom()
|
||||
protected override string SubmitText => "Get Support";
|
||||
|
||||
protected override Func<Task> SubmitAction => this.GetSupport;
|
||||
|
||||
protected override void ResetForm()
|
||||
{
|
||||
this.codingContexts.Clear();
|
||||
this.compilerMessages = string.Empty;
|
||||
@@ -50,7 +48,6 @@ public partial class AssistantCoding : AssistantBaseCore
|
||||
if (this.SettingsManager.ConfigurationData.Coding.PreselectOptions)
|
||||
{
|
||||
this.provideCompilerMessages = this.SettingsManager.ConfigurationData.Coding.PreselectCompilerMessages;
|
||||
this.providerSettings = this.SettingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == this.SettingsManager.ConfigurationData.Coding.PreselectedProvider);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -66,7 +63,6 @@ public partial class AssistantCoding : AssistantBaseCore
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
this.MightPreselectValues();
|
||||
var deferredContent = MessageBus.INSTANCE.CheckDeferredMessages<string>(Event.SEND_TO_CODING_ASSISTANT).FirstOrDefault();
|
||||
if (deferredContent is not null)
|
||||
this.questions = deferredContent;
|
||||
@@ -105,6 +101,18 @@ public partial class AssistantCoding : AssistantBaseCore
|
||||
});
|
||||
}
|
||||
|
||||
private ValueTask DeleteContext(int index)
|
||||
{
|
||||
if(this.codingContexts.Count < index + 1)
|
||||
return ValueTask.CompletedTask;
|
||||
|
||||
this.codingContexts.RemoveAt(index);
|
||||
this.form?.ResetValidation();
|
||||
|
||||
this.StateHasChanged();
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task GetSupport()
|
||||
{
|
||||
await this.form!.Validate();
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace AIStudio.Components.Pages.Coding;
|
||||
namespace AIStudio.Assistants.Coding;
|
||||
|
||||
public sealed class CodingContext(string id, CommonCodingLanguages language, string otherLanguage, string code)
|
||||
{
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
<MudTextField T="string" @bind-Text="@this.CodingContext.Id" AdornmentIcon="@Icons.Material.Filled.Numbers" Adornment="Adornment.Start" Label="(Optional) Identifier" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES" Class="mb-3"/>
|
||||
<MudStack Row="@true" AlignItems="AlignItems.Center" Class="mb-3">
|
||||
<MudStack Row="@true" Class="mb-3">
|
||||
<MudSelect T="CommonCodingLanguages" @bind-Value="@this.CodingContext.Language" AdornmentIcon="@Icons.Material.Filled.Code" Adornment="Adornment.Start" Label="Language" Variant="Variant.Outlined" Margin="Margin.Dense">
|
||||
@foreach (var language in Enum.GetValues<CommonCodingLanguages>())
|
||||
{
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AIStudio.Components.Pages.Coding;
|
||||
namespace AIStudio.Assistants.Coding;
|
||||
|
||||
public partial class CodingContextItem : ComponentBase
|
||||
{
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace AIStudio.Components.Pages.Coding;
|
||||
namespace AIStudio.Assistants.Coding;
|
||||
|
||||
public static class CommonCodingLanguageExtensions
|
||||
{
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace AIStudio.Components.Pages.Coding;
|
||||
namespace AIStudio.Assistants.Coding;
|
||||
|
||||
public enum CommonCodingLanguages
|
||||
{
|
||||
@@ -0,0 +1,23 @@
|
||||
@attribute [Route(Routes.ASSISTANT_EMAIL)]
|
||||
@inherits AssistantBaseCore
|
||||
|
||||
<MudTextSwitch Label="Is there a history, a previous conversation?" @bind-Value="@this.provideHistory" LabelOn="Yes, I provide the previous conversation" LabelOff="No, I don't provide a previous conversation" />
|
||||
@if (this.provideHistory)
|
||||
{
|
||||
<MudPaper Class="pa-3 mb-8 border-dashed border rounded-lg">
|
||||
<MudTextField T="string" @bind-Text="@this.inputHistory" Validation="@this.ValidateHistory" Label="Previous conversation" Variant="Variant.Outlined" Lines="6" AutoGrow="@true" MaxLines="12" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES" HelperText="Provide the previous conversation, e.g., the last e-mail, the last chat, etc." Class="mb-3" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.DocumentScanner"/>
|
||||
</MudPaper>
|
||||
}
|
||||
|
||||
<MudTextField T="string" @bind-Text="@this.inputGreeting" Label="(Optional) The greeting phrase to use" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Person" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES" Placeholder="Dear Colleagues" Class="mb-3"/>
|
||||
<MudTextField T="string" @bind-Text="@this.inputBulletPoints" Validation="@this.ValidateBulletPoints" AdornmentIcon="@Icons.Material.Filled.ListAlt" Adornment="Adornment.Start" Label="Your bullet points" Variant="Variant.Outlined" Lines="6" AutoGrow="@true" MaxLines="12" Class="mb-3" UserAttributes="@USER_INPUT_ATTRIBUTES" HelperText="Bullet list the content of the e-mail roughly. Use dashes (-) to separate the items." Immediate="@false" DebounceInterval="1_000" OnDebounceIntervalElapsed="@this.OnContentChanged" Placeholder="@PLACEHOLDER_BULLET_POINTS"/>
|
||||
<MudSelect T="string" Label="(Optional) Are any of your points particularly important?" MultiSelection="@true" @bind-SelectedValues="@this.selectedFoci" Variant="Variant.Outlined" Class="mb-3" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.ListAlt">
|
||||
@foreach (var contentLine in this.bulletPointsLines)
|
||||
{
|
||||
<MudSelectItem T="string" Value="@contentLine">@contentLine</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
<MudTextField T="string" @bind-Text="@this.inputName" Label="(Optional) Your name for the closing salutation" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Person" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES" HelperText="Your name for the closing salutation of your e-mail." Class="mb-3"/>
|
||||
<EnumSelection T="WritingStyles" NameFunc="@(style => style.Name())" @bind-Value="@this.selectedWritingStyle" Icon="@Icons.Material.Filled.Edit" Label="Select the writing style" ValidateSelection="@this.ValidateWritingStyle"/>
|
||||
<EnumSelection T="CommonLanguages" NameFunc="@(language => language.NameSelecting())" @bind-Value="@this.selectedTargetLanguage" ValidateSelection="@this.ValidateTargetLanguage" Icon="@Icons.Material.Filled.Translate" Label="Target language" AllowOther="@true" OtherValue="CommonLanguages.OTHER" @bind-OtherInput="@this.customTargetLanguage" ValidateOther="@this.ValidateCustomLanguage" LabelOther="Custom target language" />
|
||||
<ProviderSelection @bind-ProviderSettings="@this.providerSettings" ValidateProvider="@this.ValidatingProvider"/>
|
||||
@@ -0,0 +1,249 @@
|
||||
using System.Text;
|
||||
|
||||
using AIStudio.Chat;
|
||||
|
||||
namespace AIStudio.Assistants.EMail;
|
||||
|
||||
public partial class AssistantEMail : AssistantBaseCore
|
||||
{
|
||||
public override Tools.Components Component => Tools.Components.EMAIL_ASSISTANT;
|
||||
|
||||
protected override string Title => "E-Mail";
|
||||
|
||||
protected override string Description =>
|
||||
"""
|
||||
Provide a list of bullet points and some basic information for an e-mail. The assistant will generate an e-mail based on that input.
|
||||
""";
|
||||
|
||||
protected override string SystemPrompt =>
|
||||
$"""
|
||||
You are an automated system that writes emails. {this.SystemPromptHistory()} The user provides you with bullet points on what
|
||||
he want to address in the response. Regarding the writing style of the email: {this.selectedWritingStyle.Prompt()}
|
||||
{this.SystemPromptGreeting()} {this.SystemPromptName()} You write the email in the following language: {this.SystemPromptLanguage()}.
|
||||
""";
|
||||
|
||||
protected override IReadOnlyList<IButtonData> FooterButtons => [];
|
||||
|
||||
protected override string SubmitText => "Create email";
|
||||
|
||||
protected override Func<Task> SubmitAction => this.CreateMail;
|
||||
|
||||
protected override ChatThread ConvertToChatThread => (this.chatThread ?? new()) with
|
||||
{
|
||||
SystemPrompt = SystemPrompts.DEFAULT,
|
||||
};
|
||||
|
||||
protected override void ResetForm()
|
||||
{
|
||||
this.inputBulletPoints = string.Empty;
|
||||
this.bulletPointsLines.Clear();
|
||||
this.selectedFoci = [];
|
||||
this.provideHistory = false;
|
||||
this.inputHistory = string.Empty;
|
||||
if (!this.MightPreselectValues())
|
||||
{
|
||||
this.inputName = string.Empty;
|
||||
this.selectedTargetLanguage = CommonLanguages.AS_IS;
|
||||
this.customTargetLanguage = string.Empty;
|
||||
this.selectedWritingStyle = WritingStyles.NONE;
|
||||
this.inputGreeting = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool MightPreselectValues()
|
||||
{
|
||||
if (this.SettingsManager.ConfigurationData.EMail.PreselectOptions)
|
||||
{
|
||||
this.inputName = this.SettingsManager.ConfigurationData.EMail.SenderName;
|
||||
this.inputGreeting = this.SettingsManager.ConfigurationData.EMail.Greeting;
|
||||
this.selectedWritingStyle = this.SettingsManager.ConfigurationData.EMail.PreselectedWritingStyle;
|
||||
this.selectedTargetLanguage = this.SettingsManager.ConfigurationData.EMail.PreselectedTargetLanguage;
|
||||
this.customTargetLanguage = this.SettingsManager.ConfigurationData.EMail.PreselectOtherLanguage;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private const string PLACEHOLDER_BULLET_POINTS = """
|
||||
- The last meeting was good
|
||||
- Thank you for feedback
|
||||
- Next is milestone 3
|
||||
- I need your input by next Wednesday
|
||||
""";
|
||||
|
||||
private WritingStyles selectedWritingStyle = WritingStyles.NONE;
|
||||
private string inputGreeting = string.Empty;
|
||||
private string inputBulletPoints = string.Empty;
|
||||
private readonly List<string> bulletPointsLines = [];
|
||||
private IEnumerable<string> selectedFoci = new HashSet<string>();
|
||||
private string inputName = string.Empty;
|
||||
private CommonLanguages selectedTargetLanguage = CommonLanguages.AS_IS;
|
||||
private string customTargetLanguage = string.Empty;
|
||||
private bool provideHistory;
|
||||
private string inputHistory = string.Empty;
|
||||
|
||||
#region Overrides of ComponentBase
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
var deferredContent = MessageBus.INSTANCE.CheckDeferredMessages<string>(Event.SEND_TO_EMAIL_ASSISTANT).FirstOrDefault();
|
||||
if (deferredContent is not null)
|
||||
this.inputBulletPoints = deferredContent;
|
||||
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private string? ValidateBulletPoints(string content)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(content))
|
||||
return "Please provide some content for the e-mail.";
|
||||
|
||||
var lines = content.Split('\n', StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
if(!line.TrimStart().StartsWith('-'))
|
||||
return "Please start each line of your content list with a dash (-) to create a bullet point list.";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string? ValidateTargetLanguage(CommonLanguages language)
|
||||
{
|
||||
if(language is CommonLanguages.AS_IS)
|
||||
return "Please select a target language for the e-mail.";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string? ValidateCustomLanguage(string language)
|
||||
{
|
||||
if(this.selectedTargetLanguage == CommonLanguages.OTHER && string.IsNullOrWhiteSpace(language))
|
||||
return "Please provide a custom language.";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string? ValidateWritingStyle(WritingStyles style)
|
||||
{
|
||||
if(style == WritingStyles.NONE)
|
||||
return "Please select a writing style for the e-mail.";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string? ValidateHistory(string history)
|
||||
{
|
||||
if(this.provideHistory && string.IsNullOrWhiteSpace(history))
|
||||
return "Please provide some history for the e-mail.";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void OnContentChanged(string content)
|
||||
{
|
||||
this.bulletPointsLines.Clear();
|
||||
var previousSelectedFoci = new HashSet<string>();
|
||||
foreach (var line in content.AsSpan().EnumerateLines())
|
||||
{
|
||||
var trimmedLine = line.Trim();
|
||||
if (trimmedLine.StartsWith("-"))
|
||||
trimmedLine = trimmedLine[1..].Trim();
|
||||
|
||||
if (trimmedLine.Length == 0)
|
||||
continue;
|
||||
|
||||
var finalLine = trimmedLine.ToString();
|
||||
if(this.selectedFoci.Any(x => x.StartsWith(finalLine, StringComparison.InvariantCultureIgnoreCase)))
|
||||
previousSelectedFoci.Add(finalLine);
|
||||
|
||||
this.bulletPointsLines.Add(finalLine);
|
||||
}
|
||||
|
||||
this.selectedFoci = previousSelectedFoci;
|
||||
}
|
||||
|
||||
private string SystemPromptHistory()
|
||||
{
|
||||
if (this.provideHistory)
|
||||
return "You receive the previous conversation as context.";
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private string SystemPromptGreeting()
|
||||
{
|
||||
if(!string.IsNullOrWhiteSpace(this.inputGreeting))
|
||||
return $"Your greeting should consider the following formulation: {this.inputGreeting}.";
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private string SystemPromptName()
|
||||
{
|
||||
if(!string.IsNullOrWhiteSpace(this.inputName))
|
||||
return $"For the closing phrase of the email, please use the following name: {this.inputName}.";
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private string SystemPromptLanguage()
|
||||
{
|
||||
if(this.selectedTargetLanguage is CommonLanguages.AS_IS)
|
||||
return "Use the same language as the input";
|
||||
|
||||
if(this.selectedTargetLanguage is CommonLanguages.OTHER)
|
||||
return this.customTargetLanguage;
|
||||
|
||||
return this.selectedTargetLanguage.Name();
|
||||
}
|
||||
|
||||
private string PromptFoci()
|
||||
{
|
||||
if(!this.selectedFoci.Any())
|
||||
return string.Empty;
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("I want to amplify the following points:");
|
||||
foreach (var focus in this.selectedFoci)
|
||||
sb.AppendLine($"- {focus}");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private string PromptHistory()
|
||||
{
|
||||
if(!this.provideHistory)
|
||||
return string.Empty;
|
||||
|
||||
return $"""
|
||||
The previous conversation was:
|
||||
|
||||
```
|
||||
{this.inputHistory}
|
||||
```
|
||||
""";
|
||||
}
|
||||
|
||||
private async Task CreateMail()
|
||||
{
|
||||
await this.form!.Validate();
|
||||
if (!this.inputIsValid)
|
||||
return;
|
||||
|
||||
this.CreateChatThread();
|
||||
var time = this.AddUserRequest(
|
||||
$"""
|
||||
{this.PromptHistory()}
|
||||
|
||||
My bullet points for the e-mail are:
|
||||
|
||||
{this.inputBulletPoints}
|
||||
|
||||
{this.PromptFoci()}
|
||||
""");
|
||||
|
||||
await this.AddAIResponseAsync(time);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace AIStudio.Assistants.EMail;
|
||||
|
||||
public enum WritingStyles
|
||||
{
|
||||
NONE = 0,
|
||||
|
||||
BUSINESS_FORMAL,
|
||||
BUSINESS_INFORMAL,
|
||||
ACADEMIC,
|
||||
PERSONAL,
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
namespace AIStudio.Assistants.EMail;
|
||||
|
||||
public static class WritingStylesExtensions
|
||||
{
|
||||
public static string Name(this WritingStyles style) => style switch
|
||||
{
|
||||
WritingStyles.ACADEMIC => "Academic",
|
||||
WritingStyles.PERSONAL => "Personal",
|
||||
WritingStyles.BUSINESS_FORMAL => "Business formal",
|
||||
WritingStyles.BUSINESS_INFORMAL => "Business informal",
|
||||
|
||||
_ => "Not specified",
|
||||
};
|
||||
|
||||
public static string Prompt(this WritingStyles style) => style switch
|
||||
{
|
||||
WritingStyles.ACADEMIC => "Use an academic style for communication in an academic context like between students and professors.",
|
||||
WritingStyles.PERSONAL => "Use a personal style for communication between friends and family.",
|
||||
WritingStyles.BUSINESS_FORMAL => "Use a formal business style for this e-mail.",
|
||||
WritingStyles.BUSINESS_INFORMAL => "Use an informal business style for this e-mail.",
|
||||
|
||||
_ => "Use a formal business style for this e-mail.",
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace AIStudio.Assistants.ERI;
|
||||
|
||||
public enum AllowedLLMProviders
|
||||
{
|
||||
NONE,
|
||||
|
||||
ANY,
|
||||
SELF_HOSTED,
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace AIStudio.Assistants.ERI;
|
||||
|
||||
public static class AllowedLLMProvidersExtensions
|
||||
{
|
||||
public static string Description(this AllowedLLMProviders provider)
|
||||
{
|
||||
return provider switch
|
||||
{
|
||||
AllowedLLMProviders.NONE => "Please select what kind of LLM provider are allowed for this data source",
|
||||
AllowedLLMProviders.ANY => "Any LLM provider is allowed: users might choose a cloud-based or a self-hosted provider",
|
||||
AllowedLLMProviders.SELF_HOSTED => "Self-hosted LLM providers are allowed: users cannot choose any cloud-based provider",
|
||||
|
||||
_ => "Unknown option was selected"
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,349 @@
|
||||
@attribute [Route(Routes.ASSISTANT_ERI)]
|
||||
@using AIStudio.Settings.DataModel
|
||||
@using MudExtensions
|
||||
@inherits AssistantBaseCore
|
||||
|
||||
<MudText Typo="Typo.body1" Class="mb-3">
|
||||
You can imagine it like this: Hypothetically, when Wikipedia implemented the ERI, it would vectorize
|
||||
all pages using an embedding method. All of Wikipedia’s data would remain with Wikipedia, including the
|
||||
vector database (decentralized approach). Then, any AI Studio user could add Wikipedia as a data source to
|
||||
significantly reduce the hallucination of the LLM in knowledge questions.
|
||||
</MudText>
|
||||
|
||||
<MudText Typo="Typo.body1">
|
||||
<b>Related links:</b>
|
||||
</MudText>
|
||||
<MudList T="string" Class="mb-6">
|
||||
<MudListItem T="string" Icon="@Icons.Material.Filled.Link" Target="_blank" Href="https://github.com/MindWorkAI/ERI">ERI repository with example implementation in .NET and C#</MudListItem>
|
||||
<MudListItem T="string" Icon="@Icons.Material.Filled.Link" Target="_blank" Href="https://mindworkai.org/swagger-ui.html">Interactive documentation aka Swagger UI</MudListItem>
|
||||
</MudList>
|
||||
|
||||
<PreviewPrototype/>
|
||||
<div class="mb-6"></div>
|
||||
|
||||
<MudText Typo="Typo.h4" Class="mb-3">
|
||||
ERI server presets
|
||||
</MudText>
|
||||
|
||||
<MudText Typo="Typo.body1" Class="mb-3">
|
||||
Here you have the option to save different configurations for various ERI servers and switch between them. This is useful if
|
||||
you are responsible for multiple ERI servers.
|
||||
</MudText>
|
||||
|
||||
@if(this.SettingsManager.ConfigurationData.ERI.ERIServers.Count is 0)
|
||||
{
|
||||
<MudText Typo="Typo.body1" Class="mb-3">
|
||||
You have not yet added any ERI server presets.
|
||||
</MudText>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudList Disabled="@this.AreServerPresetsBlocked" T="DataERIServer" Class="mb-1" SelectedValue="@this.selectedERIServer" SelectedValueChanged="@this.SelectedERIServerChanged">
|
||||
@foreach (var server in this.SettingsManager.ConfigurationData.ERI.ERIServers)
|
||||
{
|
||||
<MudListItem T="DataERIServer" Icon="@Icons.Material.Filled.Settings" Value="@server">
|
||||
@server.ServerName
|
||||
</MudListItem>
|
||||
}
|
||||
</MudList>
|
||||
}
|
||||
|
||||
<MudStack Row="@true" Class="mt-1">
|
||||
<MudButton Disabled="@this.AreServerPresetsBlocked" OnClick="@this.AddERIServer" Variant="Variant.Filled" Color="Color.Primary">
|
||||
Add ERI server preset
|
||||
</MudButton>
|
||||
<MudButton OnClick="@this.RemoveERIServer" Disabled="@(this.AreServerPresetsBlocked || this.IsNoneERIServerSelected)" Variant="Variant.Filled" Color="Color.Error">
|
||||
Delete this server preset
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
|
||||
@if(this.AreServerPresetsBlocked)
|
||||
{
|
||||
<MudText Typo="Typo.body1" Class="mb-3 mt-3">
|
||||
Hint: to allow this assistant to manage multiple presets, you must enable the preselection of values in the settings.
|
||||
</MudText>
|
||||
}
|
||||
|
||||
<MudText Typo="Typo.h4" Class="mb-3 mt-6">
|
||||
Auto save
|
||||
</MudText>
|
||||
|
||||
<MudText Typo="Typo.body1" Class="mb-3">
|
||||
The ERI specification will change over time. You probably want to keep your ERI server up to date. This means you might want to
|
||||
regenerate the code for your ERI server. To avoid having to make all inputs each time, all your inputs and decisions can be
|
||||
automatically saved. Would you like this?
|
||||
</MudText>
|
||||
|
||||
@if(this.AreServerPresetsBlocked)
|
||||
{
|
||||
<MudText Typo="Typo.body1" Class="mb-3">
|
||||
Hint: to allow this assistant to automatically save your changes, you must enable the preselection of values in the settings.
|
||||
</MudText>
|
||||
}
|
||||
|
||||
<MudTextSwitch Label="Should we automatically save any input made?" Disabled="@this.AreServerPresetsBlocked" @bind-Value="@this.autoSave" LabelOn="Yes, please save my inputs" LabelOff="No, I will enter everything again or configure it manually in the settings" />
|
||||
|
||||
<hr style="width: 100%; border-width: 0.25ch;" class="mt-6"/>
|
||||
|
||||
<MudText Typo="Typo.h4" Class="mt-6 mb-1">
|
||||
Common ERI server settings
|
||||
</MudText>
|
||||
<MudTextField T="string" Disabled="@this.IsNoneERIServerSelected" @bind-Text="@this.serverName" Validation="@this.ValidateServerName" Immediate="@true" Label="ERI server name" HelperText="Please give your ERI server a name that provides information about the data source and/or its intended purpose. The name will be displayed to users in AI Studio." Counter="60" MaxLength="60" Variant="Variant.Outlined" Margin="Margin.Normal" UserAttributes="@USER_INPUT_ATTRIBUTES" Class="mb-3" OnKeyUp="() => this.ServerNameWasChanged()"/>
|
||||
<MudTextField T="string" Disabled="@this.IsNoneERIServerSelected" @bind-Text="@this.serverDescription" Validation="@this.ValidateServerDescription" Immediate="@true" Label="ERI server description" HelperText="Please provide a brief description of your ERI server. Describe or explain what your ERI server does and what data it uses for this purpose. This description will be shown to users in AI Studio." Counter="512" MaxLength="512" Variant="Variant.Outlined" Margin="Margin.Normal" Lines="3" AutoGrow="@true" MaxLines="6" UserAttributes="@USER_INPUT_ATTRIBUTES" Class="mb-3"/>
|
||||
|
||||
<MudStack Row="@true" Class="mb-3">
|
||||
<MudSelect Disabled="@this.IsNoneERIServerSelected" T="ProgrammingLanguages" @bind-Value="@this.selectedProgrammingLanguage" AdornmentIcon="@Icons.Material.Filled.Code" Adornment="Adornment.Start" Label="Programming language" Variant="Variant.Outlined" Margin="Margin.Dense" Validation="@this.ValidateProgrammingLanguage">
|
||||
@foreach (var language in Enum.GetValues<ProgrammingLanguages>())
|
||||
{
|
||||
<MudSelectItem Value="@language">@language.Name()</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
@if (this.selectedProgrammingLanguage is ProgrammingLanguages.OTHER)
|
||||
{
|
||||
<MudTextField Disabled="@this.IsNoneERIServerSelected" T="string" @bind-Text="@this.otherProgrammingLanguage" Validation="@this.ValidateOtherLanguage" Label="Other language" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
|
||||
}
|
||||
</MudStack>
|
||||
|
||||
<MudStack Row="@true" AlignItems="AlignItems.Center" Class="mb-3">
|
||||
<MudSelect Disabled="@this.IsNoneERIServerSelected" T="ERIVersion" @bind-Value="@this.selectedERIVersion" Label="ERI specification version" Variant="Variant.Outlined" Margin="Margin.Dense" Validation="@this.ValidateERIVersion">
|
||||
@foreach (var version in Enum.GetValues<ERIVersion>())
|
||||
{
|
||||
<MudSelectItem Value="@version">@version</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
<MudButton Variant="Variant.Outlined" Size="Size.Small" Disabled="@(!this.selectedERIVersion.WasSpecificationSelected() || this.IsNoneERIServerSelected)" Href="@this.selectedERIVersion.SpecificationURL()" Target="_blank">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Link" Class="mr-2"/> Download specification
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
|
||||
<MudText Typo="Typo.h4" Class="mt-9 mb-3">
|
||||
Data source settings
|
||||
</MudText>
|
||||
|
||||
<MudStack Row="@false" Spacing="1" Class="mb-3">
|
||||
<MudSelect Disabled="@this.IsNoneERIServerSelected" T="DataSources" @bind-Value="@this.selectedDataSource" AdornmentIcon="@Icons.Material.Filled.Dataset" Adornment="Adornment.Start" Label="Data source" Variant="Variant.Outlined" Margin="Margin.Dense" Validation="@this.ValidateDataSource" SelectedValuesChanged="@this.DataSourceWasChanged">
|
||||
@foreach (var dataSource in Enum.GetValues<DataSources>())
|
||||
{
|
||||
<MudSelectItem Value="@dataSource">@dataSource.Name()</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
@if (this.selectedDataSource is DataSources.CUSTOM)
|
||||
{
|
||||
<MudTextField Disabled="@this.IsNoneERIServerSelected" T="string" @bind-Text="@this.otherDataSource" Validation="@this.ValidateOtherDataSource" Label="Describe your data source" Variant="Variant.Outlined" Margin="Margin.Normal" Lines="3" AutoGrow="@true" MaxLines="6" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
|
||||
}
|
||||
</MudStack>
|
||||
|
||||
@if(this.selectedDataSource > DataSources.FILE_SYSTEM)
|
||||
{
|
||||
<MudTextField Disabled="@this.IsNoneERIServerSelected" T="string" @bind-Text="@this.dataSourceProductName" Label="Data source: product name" Validation="@this.ValidateDataSourceProductName" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES" Class="mb-3"/>
|
||||
}
|
||||
|
||||
@if (this.NeedHostnamePort())
|
||||
{
|
||||
<div class="mb-3">
|
||||
<MudStack Row="@true">
|
||||
<MudTextField Disabled="@this.IsNoneERIServerSelected" T="string" @bind-Text="@this.dataSourceHostname" Label="Data source: hostname" Validation="@this.ValidateHostname" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
|
||||
<MudNumericField Disabled="@this.IsNoneERIServerSelected" Label="Data source: port" Immediate="@true" Min="1" Max="65535" Validation="@this.ValidatePort" @bind-Value="@this.dataSourcePort" Variant="Variant.Outlined" Margin="Margin.Dense" OnKeyUp="() => this.DataSourcePortWasTyped()"/>
|
||||
</MudStack>
|
||||
@if (this.dataSourcePort < 1024)
|
||||
{
|
||||
<MudText Typo="Typo.body2">
|
||||
<b>Warning:</b> Ports below 1024 are reserved for system services. Your ERI server need to run with elevated permissions (root user).
|
||||
</MudText>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<MudText Typo="Typo.h4" Class="mt-9 mb-3">
|
||||
Authentication settings
|
||||
</MudText>
|
||||
|
||||
<MudStack Row="@false" Spacing="1" Class="mb-1">
|
||||
<MudSelectExtended
|
||||
T="Auth"
|
||||
Disabled="@this.IsNoneERIServerSelected"
|
||||
ShrinkLabel="@true"
|
||||
MultiSelection="@true"
|
||||
MultiSelectionTextFunc="@this.GetMultiSelectionAuthText"
|
||||
SelectedValues="@this.selectedAuthenticationMethods"
|
||||
Validation="@this.ValidateAuthenticationMethods"
|
||||
SelectedValuesChanged="@this.AuthenticationMethodWasChanged"
|
||||
Label="Authentication method(s)"
|
||||
Variant="Variant.Outlined"
|
||||
Margin="Margin.Dense">
|
||||
@foreach (var authMethod in Enum.GetValues<Auth>())
|
||||
{
|
||||
<MudSelectItemExtended Value="@authMethod">@authMethod.Name()</MudSelectItemExtended>
|
||||
}
|
||||
</MudSelectExtended>
|
||||
<MudTextField Disabled="@this.IsNoneERIServerSelected" T="string" @bind-Text="@this.authDescription" Label="@this.AuthDescriptionTitle()" Validation="@this.ValidateAuthDescription" Variant="Variant.Outlined" Margin="Margin.Normal" Lines="3" AutoGrow="@true" MaxLines="6" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
|
||||
</MudStack>
|
||||
|
||||
@if (this.selectedAuthenticationMethods.Contains(Auth.KERBEROS))
|
||||
{
|
||||
<MudSelect Disabled="@this.IsNoneERIServerSelected" T="OperatingSystem" @bind-Value="@this.selectedOperatingSystem" Label="Operating system on which your ERI will run" Variant="Variant.Outlined" Margin="Margin.Dense" Validation="@this.ValidateOperatingSystem" Class="mb-1">
|
||||
@foreach (var os in Enum.GetValues<OperatingSystem>())
|
||||
{
|
||||
<MudSelectItem Value="@os">@os.Name()</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
}
|
||||
|
||||
<MudText Typo="Typo.h4" Class="mt-11 mb-3">
|
||||
Data protection settings
|
||||
</MudText>
|
||||
|
||||
<MudSelect Disabled="@this.IsNoneERIServerSelected" T="AllowedLLMProviders" @bind-Value="@this.allowedLLMProviders" Label="Allowed LLM providers for this data source" Variant="Variant.Outlined" Margin="Margin.Dense" Validation="@this.ValidateAllowedLLMProviders" Class="mb-1">
|
||||
@foreach (var option in Enum.GetValues<AllowedLLMProviders>())
|
||||
{
|
||||
<MudSelectItem Value="@option">@option.Description()</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
|
||||
<MudText Typo="Typo.h4" Class="mt-11 mb-3">
|
||||
Embedding settings
|
||||
</MudText>
|
||||
|
||||
<MudText Typo="Typo.body1" Class="mb-2">
|
||||
You will likely use one or more embedding methods to encode the meaning of your data into a typically high-dimensional vector
|
||||
space. In this case, you will use a vector database to store and search these vectors (called embeddings). However, you don't
|
||||
have to use embedding methods. When your retrieval method works without any embedding, you can ignore this section. An example: You
|
||||
store files on a file server, and your retrieval method works exclusively with file names in the file system, so you don't
|
||||
need embeddings.
|
||||
</MudText>
|
||||
|
||||
<MudText Typo="Typo.body1" Class="mb-3">
|
||||
You can specify more than one embedding method. This can be useful when you want to use different embeddings for different queries
|
||||
or data types. For example, one embedding for texts, another for images, and a third for videos, etc.
|
||||
</MudText>
|
||||
|
||||
@if (!this.IsNoneERIServerSelected)
|
||||
{
|
||||
<MudTable Items="@this.embeddings" Hover="@true" Class="border-dashed border rounded-lg">
|
||||
<ColGroup>
|
||||
<col/>
|
||||
<col style="width: 34em;"/>
|
||||
<col style="width: 34em;"/>
|
||||
</ColGroup>
|
||||
<HeaderContent>
|
||||
<MudTh>Name</MudTh>
|
||||
<MudTh>Type</MudTh>
|
||||
<MudTh Style="text-align: left;">Actions</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd>@context.EmbeddingName</MudTd>
|
||||
<MudTd>@context.EmbeddingType</MudTd>
|
||||
<MudTd Style="text-align: left;">
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.Edit" Class="ma-2" OnClick="() => this.EditEmbedding(context)">
|
||||
Edit
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Error" StartIcon="@Icons.Material.Filled.Delete" Class="ma-2" OnClick="() => this.DeleteEmbedding(context)">
|
||||
Delete
|
||||
</MudButton>
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
|
||||
@if (this.embeddings.Count == 0)
|
||||
{
|
||||
<MudText Typo="Typo.h6" Class="mt-3">No embedding methods configured yet.</MudText>
|
||||
}
|
||||
}
|
||||
|
||||
<MudButton Disabled="@this.IsNoneERIServerSelected" Variant="Variant.Filled" Color="@Color.Primary" StartIcon="@Icons.Material.Filled.AddRoad" Class="mt-3 mb-6" OnClick="@this.AddEmbedding">
|
||||
Add Embedding Method
|
||||
</MudButton>
|
||||
|
||||
<MudText Typo="Typo.h4" Class="mt-6 mb-1">
|
||||
Data retrieval settings
|
||||
</MudText>
|
||||
|
||||
<MudText Typo="Typo.body1" Class="mb-2">
|
||||
For your ERI server, you need to retrieve data that matches a chat or prompt in some way. We call this the retrieval process.
|
||||
You must describe at least one such process. You may offer several retrieval processes from which users can choose. This allows
|
||||
you to test with beta users which process works better. Or you might generally want to give users the choice so they can select
|
||||
the process that best suits their circumstances.
|
||||
</MudText>
|
||||
|
||||
@if (!this.IsNoneERIServerSelected)
|
||||
{
|
||||
<MudTable Items="@this.retrievalProcesses" Hover="@true" Class="border-dashed border rounded-lg">
|
||||
<ColGroup>
|
||||
<col/>
|
||||
<col style="width: 34em;"/>
|
||||
</ColGroup>
|
||||
<HeaderContent>
|
||||
<MudTh>Name</MudTh>
|
||||
<MudTh Style="text-align: left;">Actions</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd>@context.Name</MudTd>
|
||||
<MudTd Style="text-align: left;">
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.Edit" Class="ma-2" OnClick="() => this.EditRetrievalProcess(context)">
|
||||
Edit
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Error" StartIcon="@Icons.Material.Filled.Delete" Class="ma-2" OnClick="() => this.DeleteRetrievalProcess(context)">
|
||||
Delete
|
||||
</MudButton>
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
|
||||
@if (this.retrievalProcesses.Count == 0)
|
||||
{
|
||||
<MudText Typo="Typo.h6" Class="mt-3">No retrieval process configured yet.</MudText>
|
||||
}
|
||||
}
|
||||
|
||||
<MudButton Disabled="@this.IsNoneERIServerSelected" Variant="Variant.Filled" Color="@Color.Primary" StartIcon="@Icons.Material.Filled.AddRoad" Class="mt-3 mb-6" OnClick="@this.AddRetrievalProcess">
|
||||
Add Retrieval Process
|
||||
</MudButton>
|
||||
|
||||
<MudText Typo="Typo.body1" Class="mb-1">
|
||||
You can integrate additional libraries. Perhaps you want to evaluate the prompts in advance using a machine learning method or analyze them with a text
|
||||
mining approach? Or maybe you want to preprocess images in the prompts? For such advanced scenarios, you can specify which libraries you want to use here.
|
||||
It's best to describe which library you want to integrate for which purpose. This way, the LLM that writes the ERI server for you can try to use these
|
||||
libraries effectively. This should result in less rework being necessary. If you don't know the necessary libraries, you can instead attempt to describe
|
||||
the intended use. The LLM can then attempt to choose suitable libraries. However, hallucinations can occur, and fictional libraries might be selected.
|
||||
</MudText>
|
||||
|
||||
<MudTextField Disabled="@this.IsNoneERIServerSelected" T="string" @bind-Text="@this.additionalLibraries" Label="(Optional) Additional libraries" HelperText="Do you want to include additional libraries? Then name them and briefly describe what you want to achieve with them." Variant="Variant.Outlined" Margin="Margin.Normal" Lines="3" AutoGrow="@true" MaxLines="12" UserAttributes="@USER_INPUT_ATTRIBUTES" Class="mb-3"/>
|
||||
|
||||
<MudText Typo="Typo.h4" Class="mt-9 mb-1">
|
||||
Provider selection for generation
|
||||
</MudText>
|
||||
|
||||
<MudText Typo="Typo.body1" Class="mb-2">
|
||||
The task of writing the ERI server for you is very complex. Therefore, a very powerful LLM is needed to successfully accomplish this task.
|
||||
Small local models will probably not be sufficient. Instead, try using a large cloud-based or a large self-hosted model.
|
||||
</MudText>
|
||||
|
||||
<MudText Typo="Typo.body1" Class="mb-2">
|
||||
<b>Important:</b> The LLM may need to generate many files. This reaches the request limit of most providers. Typically, only a certain number
|
||||
of requests can be made per minute, and only a maximum number of tokens can be generated per minute. AI Studio automatically considers this.
|
||||
<b>However, generating all the files takes a certain amount of time.</b> Local or self-hosted models may work without these limitations
|
||||
and can generate responses faster. AI Studio dynamically adapts its behavior and always tries to achieve the fastest possible data processing.
|
||||
</MudText>
|
||||
|
||||
<ProviderSelection @bind-ProviderSettings="@this.providerSettings" ValidateProvider="@this.ValidatingProvider"/>
|
||||
|
||||
<MudText Typo="Typo.h4" Class="mt-9 mb-1">
|
||||
Write code to file system
|
||||
</MudText>
|
||||
|
||||
<MudText Typo="Typo.body1" Class="mb-2">
|
||||
AI Studio can save the generated code to the file system. You can select a base folder for this. AI Studio ensures that no files are created
|
||||
outside of this base folder. Furthermore, we recommend that you create a Git repository in this folder. This way, you can see what changes the
|
||||
AI has made in which files.
|
||||
</MudText>
|
||||
|
||||
<MudText Typo="Typo.body1" Class="mb-2">
|
||||
When you rebuild / re-generate the ERI server code, AI Studio proceeds as follows: All files generated last time will be deleted. All
|
||||
other files you have created remain. Then, the AI generates the new files. <b>But beware:</b> It may happen that the AI generates a
|
||||
file this time that you manually created last time. In this case, your manually created file will then be overwritten. Therefore,
|
||||
you should always create a Git repository and commit or revert all changes before using this assistant. With a diff visualization,
|
||||
you can immediately see where the AI has made changes. It is best to use an IDE suitable for your selected language for this purpose.
|
||||
</MudText>
|
||||
|
||||
<MudTextSwitch Label="Should we write the generated code to the file system?" Disabled="@this.IsNoneERIServerSelected" @bind-Value="@this.writeToFilesystem" LabelOn="Yes, please write or update all generated code to the file system" LabelOff="No, just show me the code" />
|
||||
<SelectDirectory Label="Base directory where to write the code" @bind-Directory="@this.baseDirectory" Disabled="@(this.IsNoneERIServerSelected || !this.writeToFilesystem)" DirectoryDialogTitle="Select the target directory for the ERI server" Validation="@this.ValidateDirectory" />
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,9 @@
|
||||
namespace AIStudio.Assistants.ERI;
|
||||
|
||||
public enum Auth
|
||||
{
|
||||
NONE,
|
||||
KERBEROS,
|
||||
USERNAME_PASSWORD,
|
||||
TOKEN,
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
namespace AIStudio.Assistants.ERI;
|
||||
|
||||
public static class AuthExtensions
|
||||
{
|
||||
public static string Name(this Auth auth) => auth switch
|
||||
{
|
||||
Auth.NONE => "No login necessary: useful for public data sources",
|
||||
|
||||
Auth.KERBEROS => "Login by single-sign-on (SSO) using Kerberos: very complex to implement and to operate, useful for many users",
|
||||
Auth.USERNAME_PASSWORD => "Login by username and password: simple to implement and to operate, useful for few users; easy to use for users",
|
||||
Auth.TOKEN => "Login by token: simple to implement and to operate, useful for few users; unusual for many users",
|
||||
|
||||
_ => "Unknown login method"
|
||||
};
|
||||
|
||||
public static string ToPrompt(this Auth auth) => auth switch
|
||||
{
|
||||
Auth.NONE => "No login is necessary, the data source is public.",
|
||||
|
||||
Auth.KERBEROS => "Login by single-sign-on (SSO) using Kerberos.",
|
||||
Auth.USERNAME_PASSWORD => "Login by username and password.",
|
||||
Auth.TOKEN => "Login by static token per user.",
|
||||
|
||||
_ => string.Empty,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace AIStudio.Assistants.ERI;
|
||||
|
||||
public enum DataSources
|
||||
{
|
||||
NONE,
|
||||
CUSTOM,
|
||||
|
||||
FILE_SYSTEM,
|
||||
|
||||
OBJECT_STORAGE,
|
||||
KEY_VALUE_STORE,
|
||||
DOCUMENT_STORE,
|
||||
RELATIONAL_DATABASE,
|
||||
GRAPH_DATABASE,
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace AIStudio.Assistants.ERI;
|
||||
|
||||
public static class DataSourcesExtensions
|
||||
{
|
||||
public static string Name(this DataSources dataSource) => dataSource switch
|
||||
{
|
||||
DataSources.NONE => "No data source selected",
|
||||
DataSources.CUSTOM => "Custom description",
|
||||
|
||||
DataSources.FILE_SYSTEM => "File system (local or network share)",
|
||||
DataSources.OBJECT_STORAGE => "Object storage, like Amazon S3, MinIO, etc.",
|
||||
DataSources.KEY_VALUE_STORE => "Key-Value store, like Redis, etc.",
|
||||
DataSources.DOCUMENT_STORE => "Document store, like MongoDB, etc.",
|
||||
DataSources.RELATIONAL_DATABASE => "Relational database, like MySQL, PostgreSQL, etc.",
|
||||
DataSources.GRAPH_DATABASE => "Graph database, like Neo4j, ArangoDB, etc.",
|
||||
|
||||
_ => "Unknown data source"
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace AIStudio.Assistants.ERI;
|
||||
|
||||
public enum ERIVersion
|
||||
{
|
||||
NONE,
|
||||
|
||||
V1,
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
namespace AIStudio.Assistants.ERI;
|
||||
|
||||
public static class ERIVersionExtensions
|
||||
{
|
||||
public static async Task<string> ReadSpecification(this ERIVersion version, HttpClient httpClient)
|
||||
{
|
||||
try
|
||||
{
|
||||
var url = version.SpecificationURL();
|
||||
using var response = await httpClient.GetAsync(url);
|
||||
return await response.Content.ReadAsStringAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public static string SpecificationURL(this ERIVersion version)
|
||||
{
|
||||
var nameLower = version.ToString().ToLowerInvariant();
|
||||
var filename = $"{nameLower}.json";
|
||||
return $"specs/eri/{filename}";
|
||||
}
|
||||
|
||||
public static bool WasSpecificationSelected(this ERIVersion version) => version != ERIVersion.NONE;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace AIStudio.Assistants.ERI;
|
||||
|
||||
/// <summary>
|
||||
/// Represents information about the used embedding for a data source.
|
||||
/// </summary>
|
||||
/// <param name="EmbeddingType">What kind of embedding is used. For example, "Transformer Embedding," "Contextual Word
|
||||
/// Embedding," "Graph Embedding," etc.</param>
|
||||
/// <param name="EmbeddingName">Name the embedding used. This can be a library, a framework, or the name of the used
|
||||
/// algorithm.</param>
|
||||
/// <param name="Description">A short description of the embedding. Describe what the embedding is doing.</param>
|
||||
/// <param name="UsedWhen">Describe when the embedding is used. For example, when the user prompt contains certain
|
||||
/// keywords, or anytime?</param>
|
||||
/// <param name="Link">A link to the embedding's documentation or the source code. Might be null.</param>
|
||||
public readonly record struct EmbeddingInfo(
|
||||
string EmbeddingType,
|
||||
string EmbeddingName,
|
||||
string Description,
|
||||
string UsedWhen,
|
||||
string? Link);
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace AIStudio.Assistants.ERI;
|
||||
|
||||
public enum OperatingSystem
|
||||
{
|
||||
NONE,
|
||||
|
||||
WINDOWS,
|
||||
LINUX,
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace AIStudio.Assistants.ERI;
|
||||
|
||||
public static class OperatingSystemExtensions
|
||||
{
|
||||
public static string Name(this OperatingSystem os) => os switch
|
||||
{
|
||||
OperatingSystem.NONE => "No operating system specified",
|
||||
|
||||
OperatingSystem.WINDOWS => "Windows",
|
||||
OperatingSystem.LINUX => "Linux",
|
||||
|
||||
_ => "Unknown operating system"
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
namespace AIStudio.Assistants.ERI;
|
||||
|
||||
public enum ProgrammingLanguages
|
||||
{
|
||||
NONE,
|
||||
|
||||
C,
|
||||
CPP,
|
||||
CSHARP,
|
||||
GO,
|
||||
JAVA,
|
||||
JAVASCRIPT,
|
||||
JULIA,
|
||||
MATLAB,
|
||||
PHP,
|
||||
PYTHON,
|
||||
RUST,
|
||||
|
||||
OTHER,
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
namespace AIStudio.Assistants.ERI;
|
||||
|
||||
public static class ProgrammingLanguagesExtensions
|
||||
{
|
||||
public static string Name(this ProgrammingLanguages language) => language switch
|
||||
{
|
||||
ProgrammingLanguages.NONE => "No programming language selected",
|
||||
|
||||
ProgrammingLanguages.C => "C",
|
||||
ProgrammingLanguages.CPP => "C++",
|
||||
ProgrammingLanguages.CSHARP => "C#",
|
||||
ProgrammingLanguages.GO => "Go",
|
||||
ProgrammingLanguages.JAVA => "Java",
|
||||
ProgrammingLanguages.JAVASCRIPT => "JavaScript",
|
||||
ProgrammingLanguages.JULIA => "Julia",
|
||||
ProgrammingLanguages.MATLAB => "MATLAB",
|
||||
ProgrammingLanguages.PHP => "PHP",
|
||||
ProgrammingLanguages.PYTHON => "Python",
|
||||
ProgrammingLanguages.RUST => "Rust",
|
||||
|
||||
ProgrammingLanguages.OTHER => "Other",
|
||||
_ => "Unknown"
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace AIStudio.Assistants.ERI;
|
||||
|
||||
/// <summary>
|
||||
/// Information about a retrieval process, which this data source implements.
|
||||
/// </summary>
|
||||
/// <param name="Name">The name of the retrieval process, e.g., "Keyword-Based Wikipedia Article Retrieval".</param>
|
||||
/// <param name="Description">A short description of the retrieval process. What kind of retrieval process is it?</param>
|
||||
/// <param name="Link">A link to the retrieval process's documentation, paper, Wikipedia article, or the source code. Might be null.</param>
|
||||
/// <param name="ParametersDescription">A dictionary that describes the parameters of the retrieval process. The key is the parameter name,
|
||||
/// and the value is a description of the parameter. Although each parameter will be sent as a string, the description should indicate the
|
||||
/// expected type and range, e.g., 0.0 to 1.0 for a float parameter.</param>
|
||||
/// <param name="Embeddings">A list of embeddings used in this retrieval process. It might be empty in case no embedding is used.</param>
|
||||
public readonly record struct RetrievalInfo(
|
||||
string Name,
|
||||
string Description,
|
||||
string? Link,
|
||||
Dictionary<string, string>? ParametersDescription,
|
||||
List<EmbeddingInfo>? Embeddings);
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace AIStudio.Assistants.ERI;
|
||||
|
||||
public sealed class RetrievalParameter
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the parameter.
|
||||
/// </summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The description of the parameter.
|
||||
/// </summary>
|
||||
public string Description { get; set; } = string.Empty;
|
||||
}
|
||||
+2
-7
@@ -1,11 +1,6 @@
|
||||
@using AIStudio.Tools
|
||||
@attribute [Route(Path.ASSISTANT_GRAMMAR_SPELLING)]
|
||||
@attribute [Route(Routes.ASSISTANT_GRAMMAR_SPELLING)]
|
||||
@inherits AssistantBaseCore
|
||||
|
||||
<MudTextField T="string" @bind-Text="@this.inputText" Validation="@this.ValidateText" AdornmentIcon="@Icons.Material.Filled.DocumentScanner" Adornment="Adornment.Start" Label="Your input to check" Variant="Variant.Outlined" Lines="6" AutoGrow="@true" MaxLines="12" Class="mb-3" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
|
||||
<EnumSelection T="CommonLanguages" NameFunc="@(language => language.NameSelectingOptional())" @bind-Value="@this.selectedTargetLanguage" Icon="@Icons.Material.Filled.Translate" Label="Language" AllowOther="@true" OtherValue="CommonLanguages.OTHER" @bind-OtherInput="@this.customTargetLanguage" ValidateOther="@this.ValidateCustomLanguage" LabelOther="Custom language" />
|
||||
<ProviderSelection @bind-ProviderSettings="@this.providerSettings" ValidateProvider="@this.ValidatingProvider"/>
|
||||
|
||||
<MudButton Variant="Variant.Filled" Class="mb-3" OnClick="() => this.ProofreadText()">
|
||||
Proofread
|
||||
</MudButton>
|
||||
<ProviderSelection @bind-ProviderSettings="@this.providerSettings" ValidateProvider="@this.ValidatingProvider"/>
|
||||
+13
-9
@@ -1,10 +1,11 @@
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Tools;
|
||||
|
||||
namespace AIStudio.Components.Pages.GrammarSpelling;
|
||||
namespace AIStudio.Assistants.GrammarSpelling;
|
||||
|
||||
public partial class AssistantGrammarSpelling : AssistantBaseCore
|
||||
{
|
||||
public override Tools.Components Component => Tools.Components.GRAMMAR_SPELLING_ASSISTANT;
|
||||
|
||||
protected override string Title => "Grammar & Spelling Checker";
|
||||
|
||||
protected override string Description =>
|
||||
@@ -21,28 +22,33 @@ public partial class AssistantGrammarSpelling : AssistantBaseCore
|
||||
Your response includes only the corrected text. Do not explain your changes. If no changes are needed,
|
||||
you return the text unchanged.
|
||||
""";
|
||||
|
||||
protected override bool ShowResult => false;
|
||||
|
||||
protected override bool AllowProfiles => false;
|
||||
|
||||
protected override bool ShowDedicatedProgress => true;
|
||||
|
||||
protected override Func<string> Result2Copy => () => this.correctedText;
|
||||
|
||||
protected override IReadOnlyList<IButtonData> FooterButtons =>
|
||||
[
|
||||
new ButtonData("Copy result", Icons.Material.Filled.ContentCopy, Color.Default, string.Empty, () => this.CopyToClipboard(this.correctedText)),
|
||||
new SendToButton
|
||||
{
|
||||
Self = SendTo.GRAMMAR_SPELLING_ASSISTANT,
|
||||
Self = Tools.Components.GRAMMAR_SPELLING_ASSISTANT,
|
||||
UseResultingContentBlockData = false,
|
||||
GetText = () => string.IsNullOrWhiteSpace(this.correctedText) ? this.inputText : this.correctedText
|
||||
},
|
||||
];
|
||||
|
||||
protected override string SubmitText => "Proofread";
|
||||
|
||||
protected override Func<Task> SubmitAction => this.ProofreadText;
|
||||
|
||||
protected override ChatThread ConvertToChatThread => (this.chatThread ?? new()) with
|
||||
{
|
||||
SystemPrompt = SystemPrompts.DEFAULT,
|
||||
};
|
||||
|
||||
protected override void ResetFrom()
|
||||
protected override void ResetForm()
|
||||
{
|
||||
this.inputText = string.Empty;
|
||||
this.correctedText = string.Empty;
|
||||
@@ -59,7 +65,6 @@ public partial class AssistantGrammarSpelling : AssistantBaseCore
|
||||
{
|
||||
this.selectedTargetLanguage = this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectedTargetLanguage;
|
||||
this.customTargetLanguage = this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectedOtherLanguage;
|
||||
this.providerSettings = this.SettingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectedProvider);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -70,7 +75,6 @@ public partial class AssistantGrammarSpelling : AssistantBaseCore
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
this.MightPreselectValues();
|
||||
var deferredContent = MessageBus.INSTANCE.CheckDeferredMessages<string>(Event.SEND_TO_GRAMMAR_SPELLING_ASSISTANT).FirstOrDefault();
|
||||
if (deferredContent is not null)
|
||||
this.inputText = deferredContent;
|
||||
+2
-6
@@ -1,4 +1,4 @@
|
||||
@attribute [Route(Path.ASSISTANT_ICON_FINDER)]
|
||||
@attribute [Route(Routes.ASSISTANT_ICON_FINDER)]
|
||||
@inherits AssistantBaseCore
|
||||
|
||||
<MudTextField T="string" @bind-Text="@this.inputContext" Validation="@this.ValidatingContext" AdornmentIcon="@Icons.Material.Filled.Description" Adornment="Adornment.Start" Label="Your context" Variant="Variant.Outlined" Lines="3" AutoGrow="@true" MaxLines="12" Class="mb-3" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
|
||||
@@ -15,8 +15,4 @@
|
||||
<MudButton Href="@this.selectedIconSource.URL()" Target="_blank" Variant="Variant.Filled" Size="Size.Medium">Open website</MudButton>
|
||||
}
|
||||
</MudStack>
|
||||
<ProviderSelection @bind-ProviderSettings="@this.providerSettings" ValidateProvider="@this.ValidatingProvider"/>
|
||||
|
||||
<MudButton Variant="Variant.Filled" Class="mb-3" OnClick="() => this.FindIcon()">
|
||||
Find icon
|
||||
</MudButton>
|
||||
<ProviderSelection @bind-ProviderSettings="@this.providerSettings" ValidateProvider="@this.ValidatingProvider"/>
|
||||
+14
-16
@@ -1,11 +1,8 @@
|
||||
using AIStudio.Tools;
|
||||
|
||||
namespace AIStudio.Components.Pages.IconFinder;
|
||||
namespace AIStudio.Assistants.IconFinder;
|
||||
|
||||
public partial class AssistantIconFinder : AssistantBaseCore
|
||||
{
|
||||
private string inputContext = string.Empty;
|
||||
private IconSources selectedIconSource;
|
||||
public override Tools.Components Component => Tools.Components.ICON_FINDER_ASSISTANT;
|
||||
|
||||
protected override string Title => "Icon Finder";
|
||||
|
||||
@@ -27,16 +24,16 @@ public partial class AssistantIconFinder : AssistantBaseCore
|
||||
related to the keyword "buildings" might be the best match. Provide your keywords in a Markdown list without
|
||||
quotation marks.
|
||||
""";
|
||||
|
||||
protected override IReadOnlyList<IButtonData> FooterButtons =>
|
||||
[
|
||||
new SendToButton
|
||||
{
|
||||
Self = SendTo.ICON_FINDER_ASSISTANT,
|
||||
},
|
||||
];
|
||||
|
||||
protected override void ResetFrom()
|
||||
protected override bool AllowProfiles => false;
|
||||
|
||||
protected override IReadOnlyList<IButtonData> FooterButtons => [];
|
||||
|
||||
protected override string SubmitText => "Find Icon";
|
||||
|
||||
protected override Func<Task> SubmitAction => this.FindIcon;
|
||||
|
||||
protected override void ResetForm()
|
||||
{
|
||||
this.inputContext = string.Empty;
|
||||
if (!this.MightPreselectValues())
|
||||
@@ -50,18 +47,19 @@ public partial class AssistantIconFinder : AssistantBaseCore
|
||||
if (this.SettingsManager.ConfigurationData.IconFinder.PreselectOptions)
|
||||
{
|
||||
this.selectedIconSource = this.SettingsManager.ConfigurationData.IconFinder.PreselectedSource;
|
||||
this.providerSettings = this.SettingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == this.SettingsManager.ConfigurationData.IconFinder.PreselectedProvider);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private string inputContext = string.Empty;
|
||||
private IconSources selectedIconSource;
|
||||
|
||||
#region Overrides of ComponentBase
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
this.MightPreselectValues();
|
||||
var deferredContent = MessageBus.INSTANCE.CheckDeferredMessages<string>(Event.SEND_TO_ICON_FINDER_ASSISTANT).FirstOrDefault();
|
||||
if (deferredContent is not null)
|
||||
this.inputContext = deferredContent;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace AIStudio.Components.Pages.IconFinder;
|
||||
namespace AIStudio.Assistants.IconFinder;
|
||||
|
||||
public static class IconSourceExtensions
|
||||
{
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace AIStudio.Components.Pages.IconFinder;
|
||||
namespace AIStudio.Assistants.IconFinder;
|
||||
|
||||
public enum IconSources
|
||||
{
|
||||
@@ -0,0 +1,15 @@
|
||||
@attribute [Route(Routes.ASSISTANT_JOB_POSTING)]
|
||||
@inherits AssistantBaseCore
|
||||
|
||||
<MudTextField T="string" @bind-Text="@this.inputCompanyName" Label="(Optional) The company name" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Warehouse" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES" Class="mb-3"/>
|
||||
<MudTextField T="string" @bind-Text="@this.inputCountryLegalFramework" Label="Provide the country, where the company is located" Validation="@this.ValidateCountryLegalFramework" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Flag" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES" Class="mb-3" HelperText="This is important to consider the legal framework of the country."/>
|
||||
<MudTextField T="string" @bind-Text="@this.inputMandatoryInformation" Label="(Optional) Provide mandatory information" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.TextSnippet" Variant="Variant.Outlined" Lines="6" AutoGrow="@true" MaxLines="12" Class="mb-3" UserAttributes="@USER_INPUT_ATTRIBUTES" HelperText="Mandatory information that your company requires for all job postings. This can include the company description, etc." />
|
||||
<MudTextField T="string" @bind-Text="@this.inputJobDescription" Label="Job description" Validation="@this.ValidateJobDescription" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Settings" Variant="Variant.Outlined" Lines="6" AutoGrow="@true" MaxLines="12" Class="mb-3" UserAttributes="@USER_INPUT_ATTRIBUTES" HelperText="Describe what the person is supposed to do in the company. This might be just short bullet points." />
|
||||
<MudTextField T="string" @bind-Text="@this.inputQualifications" Label="(Optional) Provide necessary job qualifications" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Settings" Variant="Variant.Outlined" Lines="6" AutoGrow="@true" MaxLines="12" Class="mb-3" UserAttributes="@USER_INPUT_ATTRIBUTES" HelperText="Describe what the person should bring to the table. This might be just short bullet points." />
|
||||
<MudTextField T="string" @bind-Text="@this.inputResponsibilities" Label="(Optional) Provide job responsibilities" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Settings" Variant="Variant.Outlined" Lines="6" AutoGrow="@true" MaxLines="12" Class="mb-3" UserAttributes="@USER_INPUT_ATTRIBUTES" HelperText="Describe the responsibilities the person should take on in the company." />
|
||||
<MudTextField T="string" @bind-Text="@this.inputWorkLocation" Label="(Optional) Provide the work location" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.MyLocation" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES" Class="mb-3"/>
|
||||
<MudTextField T="string" @bind-Text="@this.inputEntryDate" Label="(Optional) Provide the entry date" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.DateRange" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES" Class="mb-3"/>
|
||||
<MudTextField T="string" @bind-Text="@this.inputValidUntil" Label="(Optional) Provide the date until the job posting is valid" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.DateRange" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES" Class="mb-3"/>
|
||||
|
||||
<EnumSelection T="CommonLanguages" NameFunc="@(language => language.NameSelectingOptional())" @bind-Value="@this.selectedTargetLanguage" Icon="@Icons.Material.Filled.Translate" Label="Target language" AllowOther="@true" OtherValue="CommonLanguages.OTHER" @bind-OtherInput="@this.customTargetLanguage" ValidateOther="@this.ValidateCustomLanguage" LabelOther="Custom target language" />
|
||||
<ProviderSelection @bind-ProviderSettings="@this.providerSettings" ValidateProvider="@this.ValidatingProvider"/>
|
||||
@@ -0,0 +1,290 @@
|
||||
using AIStudio.Chat;
|
||||
|
||||
namespace AIStudio.Assistants.JobPosting;
|
||||
|
||||
public partial class AssistantJobPostings : AssistantBaseCore
|
||||
{
|
||||
public override Tools.Components Component => Tools.Components.JOB_POSTING_ASSISTANT;
|
||||
|
||||
protected override string Title => "Job Posting";
|
||||
|
||||
protected override string Description =>
|
||||
"""
|
||||
Provide some key points about the job you want to post. The AI will then
|
||||
formulate a suggestion that you can finalize.
|
||||
""";
|
||||
|
||||
protected override string SystemPrompt =>
|
||||
$"""
|
||||
You are an experienced specialist in personnel matters. You write job postings.
|
||||
You follow the usual guidelines in the country of the posting. You ensure that
|
||||
no individuals are discriminated against or excluded by the job posting. You do
|
||||
not ask any questions and you do not repeat the task. You provide your response
|
||||
in a Markdown format. Missing information necessary for the job posting, you try
|
||||
to derive from the other details provided.
|
||||
|
||||
Structure of your response:
|
||||
---
|
||||
# <TITLE>
|
||||
<Description of the job and the company>
|
||||
|
||||
# <TASKS>
|
||||
<Describe what takes the job entails>
|
||||
|
||||
# <QUALIFICATIONS>
|
||||
<What qualifications are required>
|
||||
|
||||
# <RESPONSIBILITIES>
|
||||
<What responsibilities are associated with the job>
|
||||
|
||||
<When available, closing details such as the entry date etc.>
|
||||
|
||||
---
|
||||
|
||||
You write the job posting in the following language: {this.SystemPromptLanguage()}.
|
||||
""";
|
||||
|
||||
protected override IReadOnlyList<IButtonData> FooterButtons => [];
|
||||
|
||||
protected override string SubmitText => "Create the job posting";
|
||||
|
||||
protected override Func<Task> SubmitAction => this.CreateJobPosting;
|
||||
|
||||
protected override bool SubmitDisabled => false;
|
||||
|
||||
protected override bool AllowProfiles => false;
|
||||
|
||||
protected override ChatThread ConvertToChatThread => (this.chatThread ?? new()) with
|
||||
{
|
||||
SystemPrompt = SystemPrompts.DEFAULT,
|
||||
};
|
||||
|
||||
protected override void ResetForm()
|
||||
{
|
||||
this.inputEntryDate = string.Empty;
|
||||
this.inputValidUntil = string.Empty;
|
||||
if (!this.MightPreselectValues())
|
||||
{
|
||||
this.selectedTargetLanguage = CommonLanguages.AS_IS;
|
||||
this.customTargetLanguage = string.Empty;
|
||||
this.inputMandatoryInformation = string.Empty;
|
||||
this.inputJobDescription = string.Empty;
|
||||
this.inputQualifications = string.Empty;
|
||||
this.inputResponsibilities = string.Empty;
|
||||
this.inputCompanyName = string.Empty;
|
||||
this.inputWorkLocation = string.Empty;
|
||||
this.inputCountryLegalFramework = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool MightPreselectValues()
|
||||
{
|
||||
if (this.SettingsManager.ConfigurationData.JobPostings.PreselectOptions)
|
||||
{
|
||||
this.inputMandatoryInformation = this.SettingsManager.ConfigurationData.JobPostings.PreselectedMandatoryInformation;
|
||||
if(string.IsNullOrWhiteSpace(this.inputJobDescription))
|
||||
this.inputJobDescription = this.SettingsManager.ConfigurationData.JobPostings.PreselectedJobDescription;
|
||||
this.inputQualifications = this.SettingsManager.ConfigurationData.JobPostings.PreselectedQualifications;
|
||||
this.inputResponsibilities = this.SettingsManager.ConfigurationData.JobPostings.PreselectedResponsibilities;
|
||||
this.inputCompanyName = this.SettingsManager.ConfigurationData.JobPostings.PreselectedCompanyName;
|
||||
this.inputWorkLocation = this.SettingsManager.ConfigurationData.JobPostings.PreselectedWorkLocation;
|
||||
this.inputCountryLegalFramework = this.SettingsManager.ConfigurationData.JobPostings.PreselectedCountryLegalFramework;
|
||||
this.selectedTargetLanguage = this.SettingsManager.ConfigurationData.JobPostings.PreselectedTargetLanguage;
|
||||
this.customTargetLanguage = this.SettingsManager.ConfigurationData.JobPostings.PreselectOtherLanguage;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private string inputMandatoryInformation = string.Empty;
|
||||
private string inputJobDescription = string.Empty;
|
||||
private string inputQualifications = string.Empty;
|
||||
private string inputResponsibilities = string.Empty;
|
||||
private string inputCompanyName = string.Empty;
|
||||
private string inputEntryDate = string.Empty;
|
||||
private string inputValidUntil = string.Empty;
|
||||
private string inputWorkLocation = string.Empty;
|
||||
private string inputCountryLegalFramework = string.Empty;
|
||||
private CommonLanguages selectedTargetLanguage = CommonLanguages.AS_IS;
|
||||
private string customTargetLanguage = string.Empty;
|
||||
|
||||
#region Overrides of ComponentBase
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
var deferredContent = MessageBus.INSTANCE.CheckDeferredMessages<string>(Event.SEND_TO_JOB_POSTING_ASSISTANT).FirstOrDefault();
|
||||
if (deferredContent is not null)
|
||||
this.inputJobDescription = deferredContent;
|
||||
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private string? ValidateCustomLanguage(string language)
|
||||
{
|
||||
if(this.selectedTargetLanguage == CommonLanguages.OTHER && string.IsNullOrWhiteSpace(language))
|
||||
return "Please provide a custom target language.";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string? ValidateJobDescription(string jobDescription)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(jobDescription))
|
||||
return "Please provide a job description.";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string? ValidateCountryLegalFramework(string countryLegalFramework)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(countryLegalFramework))
|
||||
return "Please provide the country where the job is posted (legal framework).";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string SystemPromptLanguage()
|
||||
{
|
||||
if(this.selectedTargetLanguage is CommonLanguages.AS_IS)
|
||||
return "Use the same language as the input";
|
||||
|
||||
if(this.selectedTargetLanguage is CommonLanguages.OTHER)
|
||||
return this.customTargetLanguage;
|
||||
|
||||
return this.selectedTargetLanguage.Name();
|
||||
}
|
||||
|
||||
private string UserPromptMandatoryInformation()
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(this.inputMandatoryInformation))
|
||||
return string.Empty;
|
||||
|
||||
return $"""
|
||||
# Mandatory Information
|
||||
{this.inputMandatoryInformation}
|
||||
|
||||
""";
|
||||
}
|
||||
|
||||
private string UserPromptJobDescription()
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(this.inputJobDescription))
|
||||
return string.Empty;
|
||||
|
||||
return $"""
|
||||
# Job Description
|
||||
{this.inputJobDescription}
|
||||
|
||||
""";
|
||||
}
|
||||
|
||||
private string UserPromptQualifications()
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(this.inputQualifications))
|
||||
return string.Empty;
|
||||
|
||||
return $"""
|
||||
# Qualifications
|
||||
{this.inputQualifications}
|
||||
|
||||
""";
|
||||
}
|
||||
|
||||
private string UserPromptResponsibilities()
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(this.inputResponsibilities))
|
||||
return string.Empty;
|
||||
|
||||
return $"""
|
||||
# Responsibilities
|
||||
{this.inputResponsibilities}
|
||||
|
||||
""";
|
||||
}
|
||||
|
||||
private string UserPromptCompanyName()
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(this.inputCompanyName))
|
||||
return string.Empty;
|
||||
|
||||
return $"""
|
||||
# Company Name
|
||||
{this.inputCompanyName}
|
||||
|
||||
""";
|
||||
}
|
||||
|
||||
private string UserPromptEntryDate()
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(this.inputEntryDate))
|
||||
return string.Empty;
|
||||
|
||||
return $"""
|
||||
# Entry Date
|
||||
{this.inputEntryDate}
|
||||
|
||||
""";
|
||||
}
|
||||
|
||||
private string UserPromptValidUntil()
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(this.inputValidUntil))
|
||||
return string.Empty;
|
||||
|
||||
return $"""
|
||||
# Job Posting Valid Until
|
||||
{this.inputValidUntil}
|
||||
|
||||
""";
|
||||
}
|
||||
|
||||
private string UserPromptWorkLocation()
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(this.inputWorkLocation))
|
||||
return string.Empty;
|
||||
|
||||
return $"""
|
||||
# Work Location
|
||||
{this.inputWorkLocation}
|
||||
|
||||
""";
|
||||
}
|
||||
|
||||
private string UserPromptCountryLegalFramework()
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(this.inputCountryLegalFramework))
|
||||
return string.Empty;
|
||||
|
||||
return $"""
|
||||
# Country where the job is posted (legal framework)
|
||||
{this.inputCountryLegalFramework}
|
||||
|
||||
""";
|
||||
}
|
||||
|
||||
private async Task CreateJobPosting()
|
||||
{
|
||||
await this.form!.Validate();
|
||||
if (!this.inputIsValid)
|
||||
return;
|
||||
|
||||
this.CreateChatThread();
|
||||
var time = this.AddUserRequest(
|
||||
$"""
|
||||
{this.UserPromptCompanyName()}
|
||||
{this.UserPromptCountryLegalFramework()}
|
||||
{this.UserPromptMandatoryInformation()}
|
||||
{this.UserPromptJobDescription()}
|
||||
{this.UserPromptQualifications()}
|
||||
{this.UserPromptResponsibilities()}
|
||||
{this.UserPromptWorkLocation()}
|
||||
{this.UserPromptEntryDate()}
|
||||
{this.UserPromptValidUntil()}
|
||||
""");
|
||||
|
||||
await this.AddAIResponseAsync(time);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
@attribute [Route(Routes.ASSISTANT_LEGAL_CHECK)]
|
||||
@inherits AssistantBaseCore
|
||||
|
||||
@if (!this.SettingsManager.ConfigurationData.LegalCheck.HideWebContentReader)
|
||||
{
|
||||
<ReadWebContent @bind-Content="@this.inputLegalDocument" ProviderSettings="@this.providerSettings" @bind-AgentIsRunning="@this.isAgentRunning" Preselect="@(this.SettingsManager.ConfigurationData.LegalCheck.PreselectOptions && this.SettingsManager.ConfigurationData.LegalCheck.PreselectWebContentReader)" PreselectContentCleanerAgent="@(this.SettingsManager.ConfigurationData.LegalCheck.PreselectOptions && this.SettingsManager.ConfigurationData.LegalCheck.PreselectContentCleanerAgent)"/>
|
||||
}
|
||||
|
||||
<MudTextField T="string" Disabled="@this.isAgentRunning" @bind-Text="@this.inputLegalDocument" Validation="@this.ValidatingLegalDocument" AdornmentIcon="@Icons.Material.Filled.DocumentScanner" Adornment="Adornment.Start" Label="Legal document" Variant="Variant.Outlined" Lines="12" AutoGrow="@true" MaxLines="24" Class="mb-3" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
|
||||
<MudTextField T="string" Disabled="@this.isAgentRunning" @bind-Text="@this.inputQuestions" Validation="@this.ValidatingQuestions" AdornmentIcon="@Icons.Material.Filled.QuestionAnswer" Adornment="Adornment.Start" Label="Your questions" Variant="Variant.Outlined" Lines="6" AutoGrow="@true" MaxLines="12" Class="mb-3" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
|
||||
<ProviderSelection @bind-ProviderSettings="@this.providerSettings" ValidateProvider="@this.ValidatingProvider"/>
|
||||
@@ -0,0 +1,100 @@
|
||||
using AIStudio.Chat;
|
||||
|
||||
namespace AIStudio.Assistants.LegalCheck;
|
||||
|
||||
public partial class AssistantLegalCheck : AssistantBaseCore
|
||||
{
|
||||
public override Tools.Components Component => Tools.Components.LEGAL_CHECK_ASSISTANT;
|
||||
|
||||
protected override string Title => "Legal Check";
|
||||
|
||||
protected override string Description =>
|
||||
"""
|
||||
Provide a legal document and ask a question about it. This assistant does not
|
||||
replace legal advice. Consult a lawyer to get professional advice. Remember
|
||||
that LLMs can invent answers and facts. Please do not rely on this answers.
|
||||
""";
|
||||
|
||||
protected override string SystemPrompt =>
|
||||
"""
|
||||
You are an expert in legal matters. You have studied international law and are familiar with the law in
|
||||
various countries. You receive a legal document and answer the user's questions. You are a friendly and
|
||||
professional assistant. You respond to the user in the language in which the questions were asked. If
|
||||
you are unsure, unfamiliar with the legal area, or do not know the answers, you inform the user.
|
||||
Never invent facts!
|
||||
""";
|
||||
|
||||
protected override IReadOnlyList<IButtonData> FooterButtons => [];
|
||||
|
||||
protected override string SubmitText => "Ask your questions";
|
||||
|
||||
protected override Func<Task> SubmitAction => this.AksQuestions;
|
||||
|
||||
protected override bool SubmitDisabled => this.isAgentRunning;
|
||||
|
||||
protected override ChatThread ConvertToChatThread => (this.chatThread ?? new()) with
|
||||
{
|
||||
SystemPrompt = SystemPrompts.DEFAULT,
|
||||
};
|
||||
|
||||
protected override void ResetForm()
|
||||
{
|
||||
this.inputLegalDocument = string.Empty;
|
||||
this.inputQuestions = string.Empty;
|
||||
this.MightPreselectValues();
|
||||
}
|
||||
|
||||
protected override bool MightPreselectValues() => false;
|
||||
|
||||
private bool isAgentRunning;
|
||||
private string inputLegalDocument = string.Empty;
|
||||
private string inputQuestions = string.Empty;
|
||||
|
||||
#region Overrides of ComponentBase
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
var deferredContent = MessageBus.INSTANCE.CheckDeferredMessages<string>(Event.SEND_TO_LEGAL_CHECK_ASSISTANT).FirstOrDefault();
|
||||
if (deferredContent is not null)
|
||||
this.inputQuestions = deferredContent;
|
||||
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private string? ValidatingLegalDocument(string text)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(text))
|
||||
return "Please provide a legal document as input. You might copy the desired text from a document or a website.";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string? ValidatingQuestions(string text)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(text))
|
||||
return "Please provide your questions as input.";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task AksQuestions()
|
||||
{
|
||||
await this.form!.Validate();
|
||||
if (!this.inputIsValid)
|
||||
return;
|
||||
|
||||
this.CreateChatThread();
|
||||
var time = this.AddUserRequest(
|
||||
$"""
|
||||
# The legal document
|
||||
{this.inputLegalDocument}
|
||||
|
||||
# The questions
|
||||
{this.inputQuestions}
|
||||
""");
|
||||
|
||||
await this.AddAIResponseAsync(time);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
@attribute [Route(Routes.ASSISTANT_MY_TASKS)]
|
||||
@inherits AssistantBaseCore
|
||||
|
||||
<ProfileFormSelection Validation="@this.ValidateProfile" @bind-Profile="@this.currentProfile"/>
|
||||
<MudTextField T="string" @bind-Text="@this.inputText" Validation="@this.ValidatingText" AdornmentIcon="@Icons.Material.Filled.DocumentScanner" Adornment="Adornment.Start" Label="Text or email" Variant="Variant.Outlined" Lines="12" AutoGrow="@true" MaxLines="24" Class="mb-3" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
|
||||
<EnumSelection T="CommonLanguages" NameFunc="@(language => language.NameSelectingOptional())" @bind-Value="@this.selectedTargetLanguage" Icon="@Icons.Material.Filled.Translate" Label="Target language" AllowOther="@true" OtherValue="CommonLanguages.OTHER" @bind-OtherInput="@this.customTargetLanguage" ValidateOther="@this.ValidateCustomLanguage" LabelOther="Custom target language" />
|
||||
<ProviderSelection @bind-ProviderSettings="@this.providerSettings" ValidateProvider="@this.ValidatingProvider"/>
|
||||
@@ -0,0 +1,129 @@
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Settings;
|
||||
|
||||
namespace AIStudio.Assistants.MyTasks;
|
||||
|
||||
public partial class AssistantMyTasks : AssistantBaseCore
|
||||
{
|
||||
public override Tools.Components Component => Tools.Components.MY_TASKS_ASSISTANT;
|
||||
|
||||
protected override string Title => "My Tasks";
|
||||
|
||||
protected override string Description =>
|
||||
"""
|
||||
You received a cryptic email that was sent to many recipients and you are now wondering
|
||||
if you need to do something? Copy the email into the input field. You also need to select
|
||||
a personal profile. In this profile, you should describe your role in the organization.
|
||||
The AI will then try to give you hints on what your tasks might be.
|
||||
""";
|
||||
|
||||
protected override string SystemPrompt =>
|
||||
$"""
|
||||
You are a friendly and professional business expert. You receive business emails, protocols,
|
||||
reports, etc. as input. Additionally, you know the user's role in the organization. The user
|
||||
wonders if any tasks arise for them in their role based on the text. You now try to give hints
|
||||
and advice on whether and what the user should do. When you believe there are no tasks for the
|
||||
user, you tell them this. You consider typical business etiquette in your advice.
|
||||
|
||||
You write your advice in the following language: {this.SystemPromptLanguage()}.
|
||||
""";
|
||||
|
||||
protected override IReadOnlyList<IButtonData> FooterButtons => [];
|
||||
|
||||
protected override string SubmitText => "Analyze text";
|
||||
|
||||
protected override Func<Task> SubmitAction => this.AnalyzeText;
|
||||
|
||||
protected override bool ShowProfileSelection => false;
|
||||
|
||||
protected override ChatThread ConvertToChatThread => (this.chatThread ?? new()) with
|
||||
{
|
||||
SystemPrompt = SystemPrompts.DEFAULT,
|
||||
};
|
||||
|
||||
protected override void ResetForm()
|
||||
{
|
||||
this.inputText = string.Empty;
|
||||
if (!this.MightPreselectValues())
|
||||
{
|
||||
this.selectedTargetLanguage = CommonLanguages.AS_IS;
|
||||
this.customTargetLanguage = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool MightPreselectValues()
|
||||
{
|
||||
if (this.SettingsManager.ConfigurationData.MyTasks.PreselectOptions)
|
||||
{
|
||||
this.selectedTargetLanguage = this.SettingsManager.ConfigurationData.MyTasks.PreselectedTargetLanguage;
|
||||
this.customTargetLanguage = this.SettingsManager.ConfigurationData.MyTasks.PreselectOtherLanguage;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private string inputText = string.Empty;
|
||||
private CommonLanguages selectedTargetLanguage = CommonLanguages.AS_IS;
|
||||
private string customTargetLanguage = string.Empty;
|
||||
|
||||
#region Overrides of ComponentBase
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
var deferredContent = MessageBus.INSTANCE.CheckDeferredMessages<string>(Event.SEND_TO_MY_TASKS_ASSISTANT).FirstOrDefault();
|
||||
if (deferredContent is not null)
|
||||
this.inputText = deferredContent;
|
||||
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private string? ValidatingText(string text)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(text))
|
||||
return "Please provide some text as input. For example, an email.";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string? ValidateProfile(Profile profile)
|
||||
{
|
||||
if(profile == default || profile == Profile.NO_PROFILE)
|
||||
return "Please select one of your profiles.";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string? ValidateCustomLanguage(string language)
|
||||
{
|
||||
if(this.selectedTargetLanguage == CommonLanguages.OTHER && string.IsNullOrWhiteSpace(language))
|
||||
return "Please provide a custom language.";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string SystemPromptLanguage()
|
||||
{
|
||||
if(this.selectedTargetLanguage is CommonLanguages.AS_IS)
|
||||
return "Use the same language as the input";
|
||||
|
||||
if(this.selectedTargetLanguage is CommonLanguages.OTHER)
|
||||
return this.customTargetLanguage;
|
||||
|
||||
return this.selectedTargetLanguage.Name();
|
||||
}
|
||||
|
||||
private async Task AnalyzeText()
|
||||
{
|
||||
await this.form!.Validate();
|
||||
if (!this.inputIsValid)
|
||||
return;
|
||||
|
||||
this.CreateChatThread();
|
||||
var time = this.AddUserRequest(this.inputText);
|
||||
|
||||
await this.AddAIResponseAsync(time);
|
||||
}
|
||||
}
|
||||
+3
-7
@@ -1,12 +1,8 @@
|
||||
@using AIStudio.Tools
|
||||
@attribute [Route(Path.ASSISTANT_REWRITE)]
|
||||
@attribute [Route(Routes.ASSISTANT_REWRITE)]
|
||||
@inherits AssistantBaseCore
|
||||
|
||||
<MudTextField T="string" @bind-Text="@this.inputText" Validation="@this.ValidateText" AdornmentIcon="@Icons.Material.Filled.DocumentScanner" Adornment="Adornment.Start" Label="Your input to improve" Variant="Variant.Outlined" Lines="6" AutoGrow="@true" MaxLines="12" Class="mb-3" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
|
||||
<EnumSelection T="CommonLanguages" NameFunc="@(language => language.NameSelectingOptional())" @bind-Value="@this.selectedTargetLanguage" Icon="@Icons.Material.Filled.Translate" Label="Language" AllowOther="@true" OtherValue="CommonLanguages.OTHER" @bind-OtherInput="@this.customTargetLanguage" ValidateOther="@this.ValidateCustomLanguage" LabelOther="Custom language" />
|
||||
<EnumSelection T="WritingStyles" NameFunc="@(style => style.Name())" @bind-Value="@this.selectedWritingStyle" Icon="@Icons.Material.Filled.Edit" Label="Writing style" AllowOther="@false" />
|
||||
<ProviderSelection @bind-ProviderSettings="@this.providerSettings" ValidateProvider="@this.ValidatingProvider"/>
|
||||
|
||||
<MudButton Variant="Variant.Filled" Class="mb-3" OnClick="() => this.RewriteText()">
|
||||
Improve
|
||||
</MudButton>
|
||||
<EnumSelection T="SentenceStructure" NameFunc="@(voice => voice.Name())" @bind-Value="@this.selectedSentenceStructure" Icon="@Icons.Material.Filled.Person4" Label="Sentence structure" />
|
||||
<ProviderSelection @bind-ProviderSettings="@this.providerSettings" ValidateProvider="@this.ValidatingProvider"/>
|
||||
+18
-11
@@ -1,10 +1,11 @@
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Tools;
|
||||
|
||||
namespace AIStudio.Components.Pages.RewriteImprove;
|
||||
namespace AIStudio.Assistants.RewriteImprove;
|
||||
|
||||
public partial class AssistantRewriteImprove : AssistantBaseCore
|
||||
{
|
||||
public override Tools.Components Component => Tools.Components.REWRITE_ASSISTANT;
|
||||
|
||||
protected override string Title => "Rewrite & Improve Text";
|
||||
|
||||
protected override string Description =>
|
||||
@@ -19,31 +20,36 @@ public partial class AssistantRewriteImprove : AssistantBaseCore
|
||||
text. You can also correct spelling and grammar issues. You never add additional information. You never
|
||||
ask the user for additional information. Your response only contains the improved text. You do not explain
|
||||
your changes. If no changes are needed, you return the text unchanged.
|
||||
The style of the text: {this.selectedWritingStyle.Prompt()}. You follow the rules according
|
||||
to {this.SystemPromptLanguage()} in all your changes.
|
||||
The style of the text: {this.selectedWritingStyle.Prompt()}.{this.selectedSentenceStructure.Prompt()}
|
||||
You follow the rules according to {this.SystemPromptLanguage()} in all your changes.
|
||||
""";
|
||||
|
||||
protected override bool ShowResult => false;
|
||||
|
||||
protected override bool AllowProfiles => false;
|
||||
|
||||
protected override bool ShowDedicatedProgress => true;
|
||||
|
||||
protected override Func<string> Result2Copy => () => this.rewrittenText;
|
||||
|
||||
protected override IReadOnlyList<IButtonData> FooterButtons =>
|
||||
[
|
||||
new ButtonData("Copy result", Icons.Material.Filled.ContentCopy, Color.Default, string.Empty, () => this.CopyToClipboard(this.rewrittenText)),
|
||||
new SendToButton
|
||||
{
|
||||
Self = SendTo.REWRITE_ASSISTANT,
|
||||
Self = Tools.Components.REWRITE_ASSISTANT,
|
||||
UseResultingContentBlockData = false,
|
||||
GetText = () => string.IsNullOrWhiteSpace(this.rewrittenText) ? this.inputText : this.rewrittenText,
|
||||
},
|
||||
];
|
||||
|
||||
protected override string SubmitText => "Improve";
|
||||
|
||||
protected override Func<Task> SubmitAction => this.RewriteText;
|
||||
|
||||
protected override ChatThread ConvertToChatThread => (this.chatThread ?? new()) with
|
||||
{
|
||||
SystemPrompt = SystemPrompts.DEFAULT,
|
||||
};
|
||||
|
||||
protected override void ResetFrom()
|
||||
protected override void ResetForm()
|
||||
{
|
||||
this.inputText = string.Empty;
|
||||
this.rewrittenText = string.Empty;
|
||||
@@ -52,6 +58,7 @@ public partial class AssistantRewriteImprove : AssistantBaseCore
|
||||
this.selectedTargetLanguage = CommonLanguages.AS_IS;
|
||||
this.customTargetLanguage = string.Empty;
|
||||
this.selectedWritingStyle = WritingStyles.NOT_SPECIFIED;
|
||||
this.selectedSentenceStructure = SentenceStructure.NOT_SPECIFIED;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,8 +68,8 @@ public partial class AssistantRewriteImprove : AssistantBaseCore
|
||||
{
|
||||
this.selectedTargetLanguage = this.SettingsManager.ConfigurationData.RewriteImprove.PreselectedTargetLanguage;
|
||||
this.customTargetLanguage = this.SettingsManager.ConfigurationData.RewriteImprove.PreselectedOtherLanguage;
|
||||
this.providerSettings = this.SettingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == this.SettingsManager.ConfigurationData.RewriteImprove.PreselectedProvider);
|
||||
this.selectedWritingStyle = this.SettingsManager.ConfigurationData.RewriteImprove.PreselectedWritingStyle;
|
||||
this.selectedSentenceStructure = this.SettingsManager.ConfigurationData.RewriteImprove.PreselectedSentenceStructure;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -73,7 +80,6 @@ public partial class AssistantRewriteImprove : AssistantBaseCore
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
this.MightPreselectValues();
|
||||
var deferredContent = MessageBus.INSTANCE.CheckDeferredMessages<string>(Event.SEND_TO_REWRITE_ASSISTANT).FirstOrDefault();
|
||||
if (deferredContent is not null)
|
||||
this.inputText = deferredContent;
|
||||
@@ -88,6 +94,7 @@ public partial class AssistantRewriteImprove : AssistantBaseCore
|
||||
private string customTargetLanguage = string.Empty;
|
||||
private string rewrittenText = string.Empty;
|
||||
private WritingStyles selectedWritingStyle;
|
||||
private SentenceStructure selectedSentenceStructure;
|
||||
|
||||
private string? ValidateText(string text)
|
||||
{
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace AIStudio.Assistants.RewriteImprove;
|
||||
|
||||
public enum SentenceStructure
|
||||
{
|
||||
NOT_SPECIFIED = 0,
|
||||
|
||||
ACTIVE,
|
||||
PASSIVE,
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
namespace AIStudio.Assistants.RewriteImprove;
|
||||
|
||||
public static class SentenceStructureExtensions
|
||||
{
|
||||
public static string Name(this SentenceStructure sentenceStructure) => sentenceStructure switch
|
||||
{
|
||||
SentenceStructure.ACTIVE => "Active voice",
|
||||
SentenceStructure.PASSIVE => "Passive voice",
|
||||
|
||||
_ => "Not Specified",
|
||||
};
|
||||
|
||||
public static string Prompt(this SentenceStructure sentenceStructure) => sentenceStructure switch
|
||||
{
|
||||
SentenceStructure.ACTIVE => " Use an active voice for the sentence structure.",
|
||||
SentenceStructure.PASSIVE => " Use a passive voice for the sentence structure.",
|
||||
|
||||
_ => string.Empty,
|
||||
};
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace AIStudio.Components.Pages.RewriteImprove;
|
||||
namespace AIStudio.Assistants.RewriteImprove;
|
||||
|
||||
public enum WritingStyles
|
||||
{
|
||||
@@ -0,0 +1,34 @@
|
||||
namespace AIStudio.Assistants.RewriteImprove;
|
||||
|
||||
public static class WritingStylesExtensions
|
||||
{
|
||||
public static string Name(this WritingStyles style) => style switch
|
||||
{
|
||||
WritingStyles.EVERYDAY => "Everyday (personal texts, social media)",
|
||||
WritingStyles.BUSINESS => "Business (business emails, reports, presentations)",
|
||||
WritingStyles.SCIENTIFIC => "Scientific (scientific papers, research reports)",
|
||||
WritingStyles.JOURNALISTIC => "Journalistic (magazines, newspapers, news)",
|
||||
WritingStyles.LITERARY => "Literary (fiction, poetry)",
|
||||
WritingStyles.TECHNICAL => "Technical (manuals, documentation)",
|
||||
WritingStyles.MARKETING => "Marketing (advertisements, sales texts)",
|
||||
WritingStyles.ACADEMIC => "Academic (essays, seminar papers)",
|
||||
WritingStyles.LEGAL => "Legal (legal texts, contracts)",
|
||||
|
||||
_ => "Not specified",
|
||||
};
|
||||
|
||||
public static string Prompt(this WritingStyles style) => style switch
|
||||
{
|
||||
WritingStyles.EVERYDAY => "Use a everyday style like for personal texts, social media, and informal communication.",
|
||||
WritingStyles.BUSINESS => "Use a business style like for business emails, reports, and presentations. Most important is clarity and professionalism.",
|
||||
WritingStyles.SCIENTIFIC => "Use a scientific style like for scientific papers, research reports, and academic writing. Most important is precision and objectivity.",
|
||||
WritingStyles.JOURNALISTIC => "Use a journalistic style like for magazines, newspapers, and news. Most important is readability and engaging content.",
|
||||
WritingStyles.LITERARY => "Use a literary style like for fiction, poetry, and creative writing. Most important is creativity and emotional impact.",
|
||||
WritingStyles.TECHNICAL => "Use a technical style like for manuals, documentation, and technical writing. Most important is clarity and precision.",
|
||||
WritingStyles.MARKETING => "Use a marketing style like for advertisements, sales texts, and promotional content. Most important is persuasiveness and engagement.",
|
||||
WritingStyles.ACADEMIC => "Use a academic style like for essays, seminar papers, and academic writing. Most important is clarity and objectivity.",
|
||||
WritingStyles.LEGAL => "Use a legal style like for legal texts, contracts, and official documents. Most important is precision and legal correctness. Use formal legal language.",
|
||||
|
||||
_ => "Keep the style of the text as it is.",
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
@attribute [Route(Routes.ASSISTANT_SYNONYMS)]
|
||||
@inherits AssistantBaseCore
|
||||
|
||||
<MudTextField T="string" @bind-Text="@this.inputText" Validation="@this.ValidatingText" AdornmentIcon="@Icons.Material.Filled.Spellcheck" Adornment="Adornment.Start" Label="Your word or phrase" Variant="Variant.Outlined" Class="mb-3" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
|
||||
<MudTextField T="string" @bind-Text="@this.inputContext" AdornmentIcon="@Icons.Material.Filled.Description" Adornment="Adornment.Start" Lines="2" AutoGrow="@false" Label="(Optional) The context for the given word or phrase" Variant="Variant.Outlined" Class="mb-3" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
|
||||
|
||||
<EnumSelection T="CommonLanguages" NameFunc="@(language => language.NameSelectingOptional())" @bind-Value="@this.selectedLanguage" Icon="@Icons.Material.Filled.Translate" Label="Language" AllowOther="@true" OtherValue="CommonLanguages.OTHER" @bind-OtherInput="@this.customTargetLanguage" ValidateOther="@this.ValidateCustomLanguage" LabelOther="Custom target language" />
|
||||
<ProviderSelection @bind-ProviderSettings="@this.providerSettings" ValidateProvider="@this.ValidatingProvider"/>
|
||||
@@ -0,0 +1,170 @@
|
||||
using AIStudio.Chat;
|
||||
|
||||
namespace AIStudio.Assistants.Synonym;
|
||||
|
||||
public partial class AssistantSynonyms : AssistantBaseCore
|
||||
{
|
||||
public override Tools.Components Component => Tools.Components.SYNONYMS_ASSISTANT;
|
||||
|
||||
protected override string Title => "Synonyms";
|
||||
|
||||
protected override string Description =>
|
||||
"""
|
||||
Find synonyms for words or phrases.
|
||||
""";
|
||||
|
||||
protected override string SystemPrompt =>
|
||||
$"""
|
||||
You have a PhD in linguistics. Therefore, you are an expert in the {this.SystemPromptLanguage()} language.
|
||||
You receive a word or phrase as input. You might also receive some context. You then provide
|
||||
a list of synonyms as a Markdown list.
|
||||
|
||||
First, derive possible meanings from the word, phrase, and context, when available. Then, provide
|
||||
possible synonyms for each meaning.
|
||||
|
||||
Example for the word "learn" and the language English (US):
|
||||
|
||||
Derive possible meanings (*this list is not part of the output*):
|
||||
- Meaning "to learn"
|
||||
- Meaning "to retain"
|
||||
|
||||
Next, provide possible synonyms for each meaning, which is your output:
|
||||
|
||||
# Meaning "to learn"
|
||||
- absorb
|
||||
- study
|
||||
- acquire
|
||||
- advance
|
||||
- practice
|
||||
|
||||
# Meaning "to retain"
|
||||
- remember
|
||||
- note
|
||||
- realize
|
||||
|
||||
You do not ask follow-up questions and never repeat the task instructions. When you do not know
|
||||
any synonyms for the given word or phrase, you state this. Your output is always in
|
||||
the {this.SystemPromptLanguage()} language.
|
||||
""";
|
||||
|
||||
protected override bool AllowProfiles => false;
|
||||
|
||||
protected override IReadOnlyList<IButtonData> FooterButtons => [];
|
||||
|
||||
protected override string SubmitText => "Find synonyms";
|
||||
|
||||
protected override Func<Task> SubmitAction => this.FindSynonyms;
|
||||
|
||||
protected override ChatThread ConvertToChatThread => (this.chatThread ?? new()) with
|
||||
{
|
||||
SystemPrompt = SystemPrompts.DEFAULT,
|
||||
};
|
||||
|
||||
protected override void ResetForm()
|
||||
{
|
||||
this.inputText = string.Empty;
|
||||
this.inputContext = string.Empty;
|
||||
if (!this.MightPreselectValues())
|
||||
{
|
||||
this.selectedLanguage = CommonLanguages.AS_IS;
|
||||
this.customTargetLanguage = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool MightPreselectValues()
|
||||
{
|
||||
if (this.SettingsManager.ConfigurationData.Synonyms.PreselectOptions)
|
||||
{
|
||||
this.selectedLanguage = this.SettingsManager.ConfigurationData.Synonyms.PreselectedLanguage;
|
||||
this.customTargetLanguage = this.SettingsManager.ConfigurationData.Synonyms.PreselectedOtherLanguage;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private string inputText = string.Empty;
|
||||
private string inputContext = string.Empty;
|
||||
private CommonLanguages selectedLanguage;
|
||||
private string customTargetLanguage = string.Empty;
|
||||
|
||||
#region Overrides of ComponentBase
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
var deferredContent = MessageBus.INSTANCE.CheckDeferredMessages<string>(Event.SEND_TO_SYNONYMS_ASSISTANT).FirstOrDefault();
|
||||
if (deferredContent is not null)
|
||||
this.inputContext = deferredContent;
|
||||
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private string? ValidatingText(string text)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(text))
|
||||
return "Please provide a word or phrase as input.";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string? ValidateCustomLanguage(string language)
|
||||
{
|
||||
if(this.selectedLanguage == CommonLanguages.OTHER && string.IsNullOrWhiteSpace(language))
|
||||
return "Please provide a custom language.";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string SystemPromptLanguage()
|
||||
{
|
||||
var lang = this.selectedLanguage switch
|
||||
{
|
||||
CommonLanguages.AS_IS => "source",
|
||||
CommonLanguages.OTHER => this.customTargetLanguage,
|
||||
|
||||
_ => $"{this.selectedLanguage.Name()}",
|
||||
};
|
||||
|
||||
if (string.IsNullOrWhiteSpace(lang))
|
||||
return "source";
|
||||
|
||||
return lang;
|
||||
}
|
||||
|
||||
private string UserPromptContext()
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(this.inputContext))
|
||||
return string.Empty;
|
||||
|
||||
return $"""
|
||||
The given context is:
|
||||
|
||||
```
|
||||
{this.inputContext}
|
||||
```
|
||||
|
||||
""";
|
||||
}
|
||||
|
||||
private async Task FindSynonyms()
|
||||
{
|
||||
await this.form!.Validate();
|
||||
if (!this.inputIsValid)
|
||||
return;
|
||||
|
||||
this.CreateChatThread();
|
||||
var time = this.AddUserRequest(
|
||||
$"""
|
||||
{this.UserPromptContext()}
|
||||
The given word or phrase is:
|
||||
|
||||
```
|
||||
{this.inputText}
|
||||
```
|
||||
""");
|
||||
|
||||
await this.AddAIResponseAsync(time);
|
||||
}
|
||||
}
|
||||
+2
-7
@@ -1,5 +1,4 @@
|
||||
@attribute [Route(Path.ASSISTANT_SUMMARIZER)]
|
||||
@using AIStudio.Tools
|
||||
@attribute [Route(Routes.ASSISTANT_SUMMARIZER)]
|
||||
@inherits AssistantBaseCore
|
||||
|
||||
@if (!this.SettingsManager.ConfigurationData.TextSummarizer.HideWebContentReader)
|
||||
@@ -10,8 +9,4 @@
|
||||
<MudTextField T="string" Disabled="@this.isAgentRunning" @bind-Text="@this.inputText" Validation="@this.ValidatingText" AdornmentIcon="@Icons.Material.Filled.DocumentScanner" Adornment="Adornment.Start" Label="Your input" Variant="Variant.Outlined" Lines="6" AutoGrow="@true" MaxLines="12" Class="mb-3" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
|
||||
<EnumSelection T="CommonLanguages" NameFunc="@(language => language.Name())" @bind-Value="@this.selectedTargetLanguage" Icon="@Icons.Material.Filled.Translate" Label="Target language" AllowOther="@true" @bind-OtherInput="@this.customTargetLanguage" OtherValue="CommonLanguages.OTHER" LabelOther="Custom target language" ValidateOther="@this.ValidateCustomLanguage" />
|
||||
<EnumSelection T="Complexity" NameFunc="@(complexity => complexity.Name())" @bind-Value="@this.selectedComplexity" Icon="@Icons.Material.Filled.Layers" Label="Target complexity" AllowOther="@true" @bind-OtherInput="@this.expertInField" OtherValue="Complexity.SCIENTIFIC_LANGUAGE_OTHER_EXPERTS" LabelOther="Your expertise" ValidateOther="@this.ValidateExpertInField" />
|
||||
<ProviderSelection @bind-ProviderSettings="@this.providerSettings" ValidateProvider="@this.ValidatingProvider"/>
|
||||
|
||||
<MudButton Variant="Variant.Filled" Class="mb-3" OnClick="() => this.SummarizeText()" Disabled="@this.isAgentRunning">
|
||||
Summarize
|
||||
</MudButton>
|
||||
<ProviderSelection @bind-ProviderSettings="@this.providerSettings" ValidateProvider="@this.ValidatingProvider"/>
|
||||
+13
-12
@@ -1,10 +1,11 @@
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Tools;
|
||||
|
||||
namespace AIStudio.Components.Pages.TextSummarizer;
|
||||
namespace AIStudio.Assistants.TextSummarizer;
|
||||
|
||||
public partial class AssistantTextSummarizer : AssistantBaseCore
|
||||
{
|
||||
public override Tools.Components Component => Tools.Components.TEXT_SUMMARIZER_ASSISTANT;
|
||||
|
||||
protected override string Title => "Text Summarizer";
|
||||
|
||||
protected override string Description =>
|
||||
@@ -24,20 +25,22 @@ public partial class AssistantTextSummarizer : AssistantBaseCore
|
||||
a summary with the requested complexity. In any case, do not add any information.
|
||||
""";
|
||||
|
||||
protected override IReadOnlyList<IButtonData> FooterButtons =>
|
||||
[
|
||||
new SendToButton
|
||||
{
|
||||
Self = SendTo.TEXT_SUMMARIZER_ASSISTANT,
|
||||
},
|
||||
];
|
||||
protected override bool AllowProfiles => false;
|
||||
|
||||
protected override IReadOnlyList<IButtonData> FooterButtons => [];
|
||||
|
||||
protected override string SubmitText => "Summarize";
|
||||
|
||||
protected override Func<Task> SubmitAction => this.SummarizeText;
|
||||
|
||||
protected override bool SubmitDisabled => this.isAgentRunning;
|
||||
|
||||
protected override ChatThread ConvertToChatThread => (this.chatThread ?? new()) with
|
||||
{
|
||||
SystemPrompt = SystemPrompts.DEFAULT,
|
||||
};
|
||||
|
||||
protected override void ResetFrom()
|
||||
protected override void ResetForm()
|
||||
{
|
||||
this.inputText = string.Empty;
|
||||
if(!this.MightPreselectValues())
|
||||
@@ -57,7 +60,6 @@ public partial class AssistantTextSummarizer : AssistantBaseCore
|
||||
this.customTargetLanguage = this.SettingsManager.ConfigurationData.TextSummarizer.PreselectedOtherLanguage;
|
||||
this.selectedComplexity = this.SettingsManager.ConfigurationData.TextSummarizer.PreselectedComplexity;
|
||||
this.expertInField = this.SettingsManager.ConfigurationData.TextSummarizer.PreselectedExpertInField;
|
||||
this.providerSettings = this.SettingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == this.SettingsManager.ConfigurationData.TextSummarizer.PreselectedProvider);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -75,7 +77,6 @@ public partial class AssistantTextSummarizer : AssistantBaseCore
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
this.MightPreselectValues();
|
||||
var deferredContent = MessageBus.INSTANCE.CheckDeferredMessages<string>(Event.SEND_TO_TEXT_SUMMARIZER_ASSISTANT).FirstOrDefault();
|
||||
if (deferredContent is not null)
|
||||
this.inputText = deferredContent;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace AIStudio.Components.Pages.TextSummarizer;
|
||||
namespace AIStudio.Assistants.TextSummarizer;
|
||||
|
||||
public enum Complexity
|
||||
{
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace AIStudio.Components.Pages.TextSummarizer;
|
||||
namespace AIStudio.Assistants.TextSummarizer;
|
||||
|
||||
public static class ComplexityExtensions
|
||||
{
|
||||
+2
-7
@@ -1,5 +1,4 @@
|
||||
@attribute [Route(Path.ASSISTANT_TRANSLATION)]
|
||||
@using AIStudio.Tools
|
||||
@attribute [Route(Routes.ASSISTANT_TRANSLATION)]
|
||||
@inherits AssistantBaseCore
|
||||
|
||||
@if (!this.SettingsManager.ConfigurationData.Translation.HideWebContentReader)
|
||||
@@ -18,8 +17,4 @@ else
|
||||
}
|
||||
|
||||
<EnumSelection T="CommonLanguages" NameFunc="@(language => language.NameSelecting())" @bind-Value="@this.selectedTargetLanguage" ValidateSelection="@this.ValidatingTargetLanguage" Icon="@Icons.Material.Filled.Translate" Label="Target language" AllowOther="@true" OtherValue="CommonLanguages.OTHER" @bind-OtherInput="@this.customTargetLanguage" ValidateOther="@this.ValidateCustomLanguage" LabelOther="Custom target language" />
|
||||
<ProviderSelection @bind-ProviderSettings="@this.providerSettings" ValidateProvider="@this.ValidatingProvider"/>
|
||||
|
||||
<MudButton Disabled="@this.isAgentRunning" Variant="Variant.Filled" Class="mb-3" OnClick="() => this.TranslateText(force: true)">
|
||||
Translate
|
||||
</MudButton>
|
||||
<ProviderSelection @bind-ProviderSettings="@this.providerSettings" ValidateProvider="@this.ValidatingProvider"/>
|
||||
+13
-12
@@ -1,10 +1,11 @@
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Tools;
|
||||
|
||||
namespace AIStudio.Components.Pages.Translation;
|
||||
namespace AIStudio.Assistants.Translation;
|
||||
|
||||
public partial class AssistantTranslation : AssistantBaseCore
|
||||
{
|
||||
public override Tools.Components Component => Tools.Components.TRANSLATION_ASSISTANT;
|
||||
|
||||
protected override string Title => "Translation";
|
||||
|
||||
protected override string Description =>
|
||||
@@ -20,20 +21,22 @@ public partial class AssistantTranslation : AssistantBaseCore
|
||||
language requires, e.g., shorter sentences, you should split the text into shorter sentences.
|
||||
""";
|
||||
|
||||
protected override IReadOnlyList<IButtonData> FooterButtons =>
|
||||
[
|
||||
new SendToButton
|
||||
{
|
||||
Self = SendTo.TRANSLATION_ASSISTANT,
|
||||
},
|
||||
];
|
||||
protected override bool AllowProfiles => false;
|
||||
|
||||
protected override IReadOnlyList<IButtonData> FooterButtons => [];
|
||||
|
||||
protected override string SubmitText => "Translate";
|
||||
|
||||
protected override Func<Task> SubmitAction => () => this.TranslateText(true);
|
||||
|
||||
protected override bool SubmitDisabled => this.isAgentRunning;
|
||||
|
||||
protected override ChatThread ConvertToChatThread => (this.chatThread ?? new()) with
|
||||
{
|
||||
SystemPrompt = SystemPrompts.DEFAULT,
|
||||
};
|
||||
|
||||
protected override void ResetFrom()
|
||||
protected override void ResetForm()
|
||||
{
|
||||
this.inputText = string.Empty;
|
||||
this.inputTextLastTranslation = string.Empty;
|
||||
@@ -52,7 +55,6 @@ public partial class AssistantTranslation : AssistantBaseCore
|
||||
this.liveTranslation = this.SettingsManager.ConfigurationData.Translation.PreselectLiveTranslation;
|
||||
this.selectedTargetLanguage = this.SettingsManager.ConfigurationData.Translation.PreselectedTargetLanguage;
|
||||
this.customTargetLanguage = this.SettingsManager.ConfigurationData.Translation.PreselectOtherLanguage;
|
||||
this.providerSettings = this.SettingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == this.SettingsManager.ConfigurationData.Translation.PreselectedProvider);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -70,7 +72,6 @@ public partial class AssistantTranslation : AssistantBaseCore
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
this.MightPreselectValues();
|
||||
var deferredContent = MessageBus.INSTANCE.CheckDeferredMessages<string>(Event.SEND_TO_TRANSLATION_ASSISTANT).FirstOrDefault();
|
||||
if (deferredContent is not null)
|
||||
this.inputText = deferredContent;
|
||||
@@ -0,0 +1,4 @@
|
||||
@using AIStudio
|
||||
@using AIStudio.Tools
|
||||
@using AIStudio.Components
|
||||
@using MudBlazor
|
||||
@@ -1,3 +1,5 @@
|
||||
using AIStudio.Settings;
|
||||
|
||||
namespace AIStudio.Chat;
|
||||
|
||||
/// <summary>
|
||||
@@ -15,6 +17,16 @@ public sealed record ChatThread
|
||||
/// </summary>
|
||||
public Guid WorkspaceId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the provider selected for the chat thread.
|
||||
/// </summary>
|
||||
public string SelectedProvider { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the profile selected for the chat thread.
|
||||
/// </summary>
|
||||
public string SelectedProfile { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The name of the chat thread. Usually generated by an AI model or manually edited by the user.
|
||||
/// </summary>
|
||||
@@ -34,4 +46,96 @@ public sealed record ChatThread
|
||||
/// The content blocks of the chat thread.
|
||||
/// </summary>
|
||||
public List<ContentBlock> Blocks { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Prepares the system prompt for the chat thread.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The actual system prompt depends on the selected profile. If no profile is selected,
|
||||
/// the system prompt is returned as is. When a profile is selected, the system prompt
|
||||
/// is extended with the profile chosen.
|
||||
/// </remarks>
|
||||
/// <param name="settingsManager">The settings manager instance to use.</param>
|
||||
/// <param name="chatThread">The chat thread to prepare the system prompt for.</param>
|
||||
/// <param name="logger">The logger instance to use.</param>
|
||||
/// <returns>The prepared system prompt.</returns>
|
||||
public string PrepareSystemPrompt(SettingsManager settingsManager, ChatThread chatThread, ILogger logger)
|
||||
{
|
||||
//
|
||||
// Prepare the system prompt:
|
||||
//
|
||||
string systemPromptText;
|
||||
var logMessage = $"Using no profile for chat thread '{chatThread.Name}'.";
|
||||
if (string.IsNullOrWhiteSpace(chatThread.SelectedProfile))
|
||||
systemPromptText = chatThread.SystemPrompt;
|
||||
else
|
||||
{
|
||||
if(!Guid.TryParse(chatThread.SelectedProfile, out var profileId))
|
||||
systemPromptText = chatThread.SystemPrompt;
|
||||
else
|
||||
{
|
||||
if(chatThread.SelectedProfile == Profile.NO_PROFILE.Id || profileId == Guid.Empty)
|
||||
systemPromptText = chatThread.SystemPrompt;
|
||||
else
|
||||
{
|
||||
var profile = settingsManager.ConfigurationData.Profiles.FirstOrDefault(x => x.Id == chatThread.SelectedProfile);
|
||||
if(profile == default)
|
||||
systemPromptText = chatThread.SystemPrompt;
|
||||
else
|
||||
{
|
||||
logMessage = $"Using profile '{profile.Name}' for chat thread '{chatThread.Name}'.";
|
||||
systemPromptText = $"""
|
||||
{chatThread.SystemPrompt}
|
||||
|
||||
{profile.ToSystemPrompt()}
|
||||
""";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.LogInformation(logMessage);
|
||||
return systemPromptText;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a content block from this chat thread.
|
||||
/// </summary>
|
||||
/// <param name="content">The content block to remove.</param>
|
||||
/// <param name="removeForRegenerate">Indicates whether the content block is removed for
|
||||
/// regeneration purposes. True, when the content block is removed for regeneration purposes,
|
||||
/// which will not remove the previous user block if it is hidden from the user.</param>
|
||||
public void Remove(IContent content, bool removeForRegenerate = false)
|
||||
{
|
||||
var block = this.Blocks.FirstOrDefault(x => x.Content == content);
|
||||
if(block is null)
|
||||
return;
|
||||
|
||||
//
|
||||
// Remove the previous user block if it is hidden from the user. Otherwise,
|
||||
// the experience might be confusing for the user.
|
||||
//
|
||||
// Explanation, using the ERI assistant as an example:
|
||||
// - The ERI assistant generates for every file a hidden user prompt.
|
||||
// - In the UI, the user can only see the AI's responses, not the hidden user prompts.
|
||||
// - Now, the user removes one AI response
|
||||
// - The hidden user prompt is still there, but the user can't see it.
|
||||
// - Since the user prompt is hidden, neither is it possible to remove nor edit it.
|
||||
// - This method solves this issue by removing the hidden user prompt when the AI response is removed.
|
||||
//
|
||||
if (block.Role is ChatRole.AI && !removeForRegenerate)
|
||||
{
|
||||
var sortedBlocks = this.Blocks.OrderBy(x => x.Time).ToList();
|
||||
var index = sortedBlocks.IndexOf(block);
|
||||
if (index > 0)
|
||||
{
|
||||
var previousBlock = sortedBlocks[index - 1];
|
||||
if (previousBlock.Role is ChatRole.USER && previousBlock.HideFromUser)
|
||||
this.Blocks.Remove(previousBlock);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the block from the chat thread:
|
||||
this.Blocks.Remove(block);
|
||||
}
|
||||
}
|
||||
@@ -18,10 +18,15 @@ public class ContentBlock
|
||||
/// <summary>
|
||||
/// The content of the block.
|
||||
/// </summary>
|
||||
public IContent? Content { get; init; } = null;
|
||||
public IContent? Content { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The role of the content block in the chat thread, e.g., user, AI, etc.
|
||||
/// </summary>
|
||||
public ChatRole Role { get; init; } = ChatRole.NONE;
|
||||
|
||||
/// <summary>
|
||||
/// Should the content block be hidden from the user?
|
||||
/// </summary>
|
||||
public bool HideFromUser { get; set; }
|
||||
}
|
||||
@@ -12,7 +12,33 @@
|
||||
<MudText Typo="Typo.body1">@this.Role.ToName() (@this.Time)</MudText>
|
||||
</CardHeaderContent>
|
||||
<CardHeaderActions>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.ContentCopy" Color="Color.Default" OnClick="@this.CopyToClipboard" />
|
||||
@if (this.IsSecondToLastBlock && this.Role is ChatRole.USER && this.EditLastUserBlockFunc is not null)
|
||||
{
|
||||
<MudTooltip Text="Edit" Placement="Placement.Bottom">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Edit" Color="Color.Default" OnClick="@this.EditLastUserBlock"/>
|
||||
</MudTooltip>
|
||||
}
|
||||
@if (this.IsLastContentBlock && this.Role is ChatRole.USER && this.EditLastBlockFunc is not null)
|
||||
{
|
||||
<MudTooltip Text="Edit" Placement="Placement.Bottom">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Edit" Color="Color.Default" OnClick="@this.EditLastBlock"/>
|
||||
</MudTooltip>
|
||||
}
|
||||
@if (this.IsLastContentBlock && this.Role is ChatRole.AI && this.RegenerateFunc is not null)
|
||||
{
|
||||
<MudTooltip Text="Regenerate" Placement="Placement.Bottom">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Recycling" Color="Color.Default" OnClick="@this.RegenerateBlock"/>
|
||||
</MudTooltip>
|
||||
}
|
||||
@if (this.RemoveBlockFunc is not null)
|
||||
{
|
||||
<MudTooltip Text="Removes this block" Placement="Placement.Bottom">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Delete" Color="Color.Error" OnClick="@this.RemoveBlock"/>
|
||||
</MudTooltip>
|
||||
}
|
||||
<MudTooltip Text="Copies the content to the clipboard" Placement="Placement.Bottom">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.ContentCopy" Color="Color.Default" OnClick="@this.CopyToClipboard"/>
|
||||
</MudTooltip>
|
||||
</CardHeaderActions>
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
@@ -44,7 +70,7 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudMarkdown Value="@textContent.Text" OverrideHeaderTypo="@Markdown.OverrideHeaderTypo"/>
|
||||
<MudMarkdown Value="@textContent.Text" OverrideHeaderTypo="@Markdown.OverrideHeaderTypo" CodeBlockTheme="@this.CodeColorPalette"/>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using AIStudio.Tools;
|
||||
using AIStudio.Settings;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
using RustService = AIStudio.Tools.RustService;
|
||||
|
||||
namespace AIStudio.Chat;
|
||||
|
||||
/// <summary>
|
||||
@@ -39,14 +41,38 @@ public partial class ContentBlockComponent : ComponentBase
|
||||
[Parameter]
|
||||
public string Class { get; set; } = string.Empty;
|
||||
|
||||
[Inject]
|
||||
private Rust Rust { get; init; } = null!;
|
||||
[Parameter]
|
||||
public bool IsLastContentBlock { get; set; } = false;
|
||||
|
||||
[Parameter]
|
||||
public bool IsSecondToLastBlock { get; set; } = false;
|
||||
|
||||
[Parameter]
|
||||
public Func<IContent, Task>? RemoveBlockFunc { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<IContent, Task>? RegenerateFunc { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<IContent, Task>? EditLastBlockFunc { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<IContent, Task>? EditLastUserBlockFunc { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<bool> RegenerateEnabled { get; set; } = () => false;
|
||||
|
||||
[Inject]
|
||||
private IJSRuntime JsRuntime { get; init; } = null!;
|
||||
private RustService RustService { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private ISnackbar Snackbar { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private SettingsManager SettingsManager { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private IDialogService DialogService { get; init; } = null!;
|
||||
|
||||
private bool HideContent { get; set; }
|
||||
|
||||
@@ -67,7 +93,7 @@ public partial class ContentBlockComponent : ComponentBase
|
||||
private async Task AfterStreaming()
|
||||
{
|
||||
// Might be called from a different thread, so we need to invoke the UI thread:
|
||||
await this.InvokeAsync(() =>
|
||||
await this.InvokeAsync(async () =>
|
||||
{
|
||||
//
|
||||
// Issue we try to solve: When the content changes during streaming,
|
||||
@@ -86,6 +112,9 @@ public partial class ContentBlockComponent : ComponentBase
|
||||
|
||||
// Let Blazor update the UI, i.e., to see the render tree diff:
|
||||
this.StateHasChanged();
|
||||
|
||||
// Inform the chat that the streaming is done:
|
||||
await MessageBus.INSTANCE.SendMessage<bool>(this, Event.CHAT_STREAMING_DONE);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -100,7 +129,7 @@ public partial class ContentBlockComponent : ComponentBase
|
||||
{
|
||||
case ContentType.TEXT:
|
||||
var textContent = (ContentText) this.Content;
|
||||
await this.Rust.CopyText2Clipboard(this.JsRuntime, this.Snackbar, textContent.Text);
|
||||
await this.RustService.CopyText2Clipboard(this.Snackbar, textContent.Text);
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -115,4 +144,68 @@ public partial class ContentBlockComponent : ComponentBase
|
||||
}
|
||||
|
||||
private string CardClasses => $"my-2 rounded-lg {this.Class}";
|
||||
|
||||
private CodeBlockTheme CodeColorPalette => this.SettingsManager.IsDarkMode ? CodeBlockTheme.Dark : CodeBlockTheme.Default;
|
||||
|
||||
private async Task RemoveBlock()
|
||||
{
|
||||
if (this.RemoveBlockFunc is null)
|
||||
return;
|
||||
|
||||
var remove = await this.DialogService.ShowMessageBox(
|
||||
"Remove Message",
|
||||
"Do you really want to remove this message?",
|
||||
"Yes, remove it",
|
||||
"No, keep it");
|
||||
|
||||
if (remove.HasValue && remove.Value)
|
||||
await this.RemoveBlockFunc(this.Content);
|
||||
}
|
||||
|
||||
private async Task RegenerateBlock()
|
||||
{
|
||||
if (this.RegenerateFunc is null)
|
||||
return;
|
||||
|
||||
if(this.Role is not ChatRole.AI)
|
||||
return;
|
||||
|
||||
var regenerate = await this.DialogService.ShowMessageBox(
|
||||
"Regenerate Message",
|
||||
"Do you really want to regenerate this message?",
|
||||
"Yes, regenerate it",
|
||||
"No, keep it");
|
||||
|
||||
if (regenerate.HasValue && regenerate.Value)
|
||||
await this.RegenerateFunc(this.Content);
|
||||
}
|
||||
|
||||
private async Task EditLastBlock()
|
||||
{
|
||||
if (this.EditLastBlockFunc is null)
|
||||
return;
|
||||
|
||||
if(this.Role is not ChatRole.USER)
|
||||
return;
|
||||
|
||||
await this.EditLastBlockFunc(this.Content);
|
||||
}
|
||||
|
||||
private async Task EditLastUserBlock()
|
||||
{
|
||||
if (this.EditLastUserBlockFunc is null)
|
||||
return;
|
||||
|
||||
if(this.Role is not ChatRole.USER)
|
||||
return;
|
||||
|
||||
var edit = await this.DialogService.ShowMessageBox(
|
||||
"Edit Message",
|
||||
"Do you really want to edit this message? In order to edit this message, the AI response will be deleted.",
|
||||
"Yes, remove the AI response and edit it",
|
||||
"No, keep it");
|
||||
|
||||
if (edit.HasValue && edit.Value)
|
||||
await this.EditLastUserBlockFunc(this.Content);
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ public sealed class ContentImage : IContent
|
||||
public Func<Task> StreamingEvent { get; set; } = () => Task.CompletedTask;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task CreateFromProviderAsync(IProvider provider, IJSRuntime jsRuntime, SettingsManager settings, Model chatModel, ChatThread chatChatThread, CancellationToken token = default)
|
||||
public Task CreateFromProviderAsync(IProvider provider, SettingsManager settings, Model chatModel, ChatThread chatChatThread, CancellationToken token = default)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@@ -35,18 +35,18 @@ public sealed class ContentText : IContent
|
||||
public Func<Task> StreamingEvent { get; set; } = () => Task.CompletedTask;
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task CreateFromProviderAsync(IProvider provider, IJSRuntime jsRuntime, SettingsManager settings, Model chatModel, ChatThread? chatThread, CancellationToken token = default)
|
||||
public async Task CreateFromProviderAsync(IProvider provider, SettingsManager settings, Model chatModel, ChatThread? chatThread, CancellationToken token = default)
|
||||
{
|
||||
if(chatThread is null)
|
||||
return;
|
||||
|
||||
// Store the last time we got a response. We use this later,
|
||||
// Store the last time we got a response. We use this later
|
||||
// to determine whether we should notify the UI about the
|
||||
// new content or not. Depends on the energy saving mode
|
||||
// the user chose.
|
||||
var last = DateTimeOffset.Now;
|
||||
|
||||
// Start another thread by using a task, to uncouple
|
||||
// Start another thread by using a task to uncouple
|
||||
// the UI thread from the AI processing:
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
@@ -54,7 +54,7 @@ public sealed class ContentText : IContent
|
||||
this.InitialRemoteWait = true;
|
||||
|
||||
// Iterate over the responses from the AI:
|
||||
await foreach (var deltaText in provider.StreamChatCompletion(jsRuntime, settings, chatModel, chatThread, token))
|
||||
await foreach (var deltaText in provider.StreamChatCompletion(chatModel, chatThread, settings, token))
|
||||
{
|
||||
// When the user cancels the request, we stop the loop:
|
||||
if (token.IsCancellationRequested)
|
||||
@@ -89,7 +89,7 @@ public sealed class ContentText : IContent
|
||||
}
|
||||
|
||||
// Stop the waiting animation (in case the loop
|
||||
// was stopped or no content was received):
|
||||
// was stopped, or no content was received):
|
||||
this.InitialRemoteWait = false;
|
||||
this.IsStreaming = false;
|
||||
}, token);
|
||||
|
||||
@@ -42,5 +42,5 @@ public interface IContent
|
||||
/// <summary>
|
||||
/// Uses the provider to create the content.
|
||||
/// </summary>
|
||||
public Task CreateFromProviderAsync(IProvider provider, IJSRuntime jsRuntime, SettingsManager settings, Model chatModel, ChatThread chatChatThread, CancellationToken token = default);
|
||||
public Task CreateFromProviderAsync(IProvider provider, SettingsManager settings, Model chatModel, ChatThread chatChatThread, CancellationToken token = default);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace AIStudio.Chat;
|
||||
|
||||
public static class KnownWorkspaces
|
||||
{
|
||||
public static readonly Guid BIAS_WORKSPACE_ID = Guid.Parse("82050a4e-ee92-43d7-8ee5-ab512f847e02");
|
||||
public static readonly Guid ERI_SERVER_WORKSPACE_ID = Guid.Parse("8ec09cd3-9da7-4736-b245-2d8b67fc342f");
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
@* ReSharper disable Html.PathError *@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>MindWork AI Studio</title>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<base href="/"/>
|
||||
|
||||
<link rel="icon" type="image/png" href="favicon.png"/>
|
||||
<link href="system/MudBlazor/MudBlazor.min.css" rel="stylesheet" />
|
||||
<link href="system/MudBlazor.Markdown/MudBlazor.Markdown.min.css" rel="stylesheet" />
|
||||
<link href="app.css" rel="stylesheet" />
|
||||
<HeadOutlet/>
|
||||
<script src="diff.js"></script>
|
||||
</head>
|
||||
|
||||
<body style="overflow: hidden;">
|
||||
<Routes @rendermode="new InteractiveServerRenderMode(prerender: false)"/>
|
||||
<script src="_framework/blazor.web.js" autostart="false"></script>
|
||||
<script src="boot.js"></script>
|
||||
<script src="system/MudBlazor/MudBlazor.min.js"></script>
|
||||
<script src="system/MudBlazor.Markdown/MudBlazor.Markdown.min.js"></script>
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,78 +0,0 @@
|
||||
@using AIStudio.Chat
|
||||
@using AIStudio.Components.Pages
|
||||
@using AIStudio.Tools
|
||||
<MudText Typo="Typo.h3" Class="mb-2 mr-3">
|
||||
@(this.Title)
|
||||
</MudText>
|
||||
|
||||
<InnerScrolling HeaderHeight="12.3em">
|
||||
<ChildContent>
|
||||
<MudForm @ref="@(this.form)" @bind-IsValid="@(this.inputIsValid)" @bind-Errors="@(this.inputIssues)" Class="pr-2">
|
||||
<MudText Typo="Typo.body1" Align="Align.Justify" Class="mb-6">
|
||||
@(this.Description)
|
||||
</MudText>
|
||||
|
||||
@if (this.Body is not null)
|
||||
{
|
||||
@(this.Body)
|
||||
}
|
||||
</MudForm>
|
||||
<Issues IssuesData="@(this.inputIssues)"/>
|
||||
|
||||
@if (this.ShowDedicatedProgress && this.isProcessing)
|
||||
{
|
||||
<MudProgressLinear Color="Color.Primary" Indeterminate="true" Class="mb-6" />
|
||||
}
|
||||
<div id="@ASSISTANT_RESULT_DIV_ID" class="mr-2 mt-3">
|
||||
@if (this.ShowResult && this.resultingContentBlock is not null)
|
||||
{
|
||||
<ContentBlockComponent Role="@(this.resultingContentBlock.Role)" Type="@(this.resultingContentBlock.ContentType)" Time="@(this.resultingContentBlock.Time)" Content="@(this.resultingContentBlock.Content)"/>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div id="@AFTER_RESULT_DIV_ID" class="mt-3">
|
||||
</div>
|
||||
|
||||
@if (this.FooterButtons.Count > 0)
|
||||
{
|
||||
<MudStack Row="@true" Wrap="Wrap.Wrap" Class="mt-3 mr-2">
|
||||
@foreach (var button in this.FooterButtons)
|
||||
{
|
||||
switch (button)
|
||||
{
|
||||
case ButtonData buttonData when !string.IsNullOrWhiteSpace(buttonData.Tooltip):
|
||||
<MudTooltip Text="@buttonData.Tooltip">
|
||||
<MudButton Variant="Variant.Filled" Color="@buttonData.Color" StartIcon="@GetButtonIcon(buttonData.Icon)" OnClick="async () => await buttonData.AsyncAction()">
|
||||
@buttonData.Text
|
||||
</MudButton>
|
||||
</MudTooltip>
|
||||
break;
|
||||
|
||||
case ButtonData buttonData:
|
||||
<MudButton Variant="Variant.Filled" Color="@buttonData.Color" StartIcon="@GetButtonIcon(buttonData.Icon)" OnClick="async () => await buttonData.AsyncAction()">
|
||||
@buttonData.Text
|
||||
</MudButton>
|
||||
break;
|
||||
|
||||
case SendToButton sendToButton:
|
||||
<MudMenu StartIcon="@Icons.Material.Filled.Apps" EndIcon="@Icons.Material.Filled.KeyboardArrowDown" Label="Send to ..." Variant="Variant.Filled" Color="Color.Info">
|
||||
@foreach (var assistant in Enum.GetValues<SendTo>().OrderBy(n => n.Name().Length))
|
||||
{
|
||||
if(assistant is SendTo.NONE || sendToButton.Self == assistant)
|
||||
continue;
|
||||
|
||||
<MudMenuItem OnClick="() => this.SendToAssistant(assistant, sendToButton)">
|
||||
@assistant.Name()
|
||||
</MudMenuItem>
|
||||
}
|
||||
</MudMenu>
|
||||
break;
|
||||
}
|
||||
}
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Warning" StartIcon="@Icons.Material.Filled.Refresh" OnClick="() => this.InnerResetForm()">
|
||||
Reset
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
}
|
||||
</ChildContent>
|
||||
</InnerScrolling>
|
||||
@@ -1,232 +0,0 @@
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Components.Pages;
|
||||
using AIStudio.Provider;
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Tools;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
using Path = AIStudio.Components.Pages.Path;
|
||||
|
||||
namespace AIStudio.Components;
|
||||
|
||||
public abstract partial class AssistantBase : ComponentBase
|
||||
{
|
||||
[Inject]
|
||||
protected SettingsManager SettingsManager { get; set; } = null!;
|
||||
|
||||
[Inject]
|
||||
protected IJSRuntime JsRuntime { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
protected ThreadSafeRandom RNG { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
protected ISnackbar Snackbar { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
protected Rust Rust { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
protected NavigationManager NavigationManager { get; init; } = null!;
|
||||
|
||||
internal const string AFTER_RESULT_DIV_ID = "afterAssistantResult";
|
||||
internal const string ASSISTANT_RESULT_DIV_ID = "assistantResult";
|
||||
|
||||
protected abstract string Title { get; }
|
||||
|
||||
protected abstract string Description { get; }
|
||||
|
||||
protected abstract string SystemPrompt { get; }
|
||||
|
||||
protected abstract void ResetFrom();
|
||||
|
||||
protected abstract bool MightPreselectValues();
|
||||
|
||||
private protected virtual RenderFragment? Body => null;
|
||||
|
||||
protected virtual bool ShowResult => true;
|
||||
|
||||
protected virtual bool ShowDedicatedProgress => false;
|
||||
|
||||
protected virtual ChatThread ConvertToChatThread => this.chatThread ?? new();
|
||||
|
||||
protected virtual IReadOnlyList<IButtonData> FooterButtons => [];
|
||||
|
||||
protected static readonly Dictionary<string, object?> USER_INPUT_ATTRIBUTES = new();
|
||||
|
||||
protected AIStudio.Settings.Provider providerSettings;
|
||||
protected MudForm? form;
|
||||
protected bool inputIsValid;
|
||||
|
||||
protected ChatThread? chatThread;
|
||||
private ContentBlock? resultingContentBlock;
|
||||
private string[] inputIssues = [];
|
||||
private bool isProcessing;
|
||||
|
||||
#region Overrides of ComponentBase
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
// Configure the spellchecking for the user input:
|
||||
this.SettingsManager.InjectSpellchecking(USER_INPUT_ATTRIBUTES);
|
||||
|
||||
await base.OnParametersSetAsync();
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
// Reset the validation when not editing and on the first render.
|
||||
// We don't want to show validation errors when the user opens the dialog.
|
||||
if(firstRender)
|
||||
this.form?.ResetValidation();
|
||||
|
||||
await base.OnAfterRenderAsync(firstRender);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
protected string? ValidatingProvider(AIStudio.Settings.Provider provider)
|
||||
{
|
||||
if(provider.UsedProvider == Providers.NONE)
|
||||
return "Please select a provider.";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void CreateChatThread()
|
||||
{
|
||||
this.chatThread = new()
|
||||
{
|
||||
WorkspaceId = Guid.Empty,
|
||||
ChatId = Guid.NewGuid(),
|
||||
Name = string.Empty,
|
||||
Seed = this.RNG.Next(),
|
||||
SystemPrompt = this.SystemPrompt,
|
||||
Blocks = [],
|
||||
};
|
||||
}
|
||||
|
||||
protected DateTimeOffset AddUserRequest(string request)
|
||||
{
|
||||
var time = DateTimeOffset.Now;
|
||||
this.chatThread!.Blocks.Add(new ContentBlock
|
||||
{
|
||||
Time = time,
|
||||
ContentType = ContentType.TEXT,
|
||||
Role = ChatRole.USER,
|
||||
Content = new ContentText
|
||||
{
|
||||
Text = request,
|
||||
},
|
||||
});
|
||||
|
||||
return time;
|
||||
}
|
||||
|
||||
protected async Task<string> AddAIResponseAsync(DateTimeOffset time)
|
||||
{
|
||||
var aiText = new ContentText
|
||||
{
|
||||
// We have to wait for the remote
|
||||
// for the content stream:
|
||||
InitialRemoteWait = true,
|
||||
};
|
||||
|
||||
this.resultingContentBlock = new ContentBlock
|
||||
{
|
||||
Time = time,
|
||||
ContentType = ContentType.TEXT,
|
||||
Role = ChatRole.AI,
|
||||
Content = aiText,
|
||||
};
|
||||
|
||||
this.chatThread?.Blocks.Add(this.resultingContentBlock);
|
||||
this.isProcessing = true;
|
||||
this.StateHasChanged();
|
||||
|
||||
// Use the selected provider to get the AI response.
|
||||
// By awaiting this line, we wait for the entire
|
||||
// content to be streamed.
|
||||
await aiText.CreateFromProviderAsync(this.providerSettings.CreateProvider(), this.JsRuntime, this.SettingsManager, this.providerSettings.Model, this.chatThread);
|
||||
|
||||
this.isProcessing = false;
|
||||
this.StateHasChanged();
|
||||
|
||||
// Return the AI response:
|
||||
return aiText.Text;
|
||||
}
|
||||
|
||||
protected async Task CopyToClipboard(string text)
|
||||
{
|
||||
await this.Rust.CopyText2Clipboard(this.JsRuntime, this.Snackbar, text);
|
||||
}
|
||||
|
||||
private static string? GetButtonIcon(string icon)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(icon))
|
||||
return null;
|
||||
|
||||
return icon;
|
||||
}
|
||||
|
||||
private Task SendToAssistant(SendTo destination, SendToButton sendToButton)
|
||||
{
|
||||
var contentToSend = sendToButton.UseResultingContentBlockData switch
|
||||
{
|
||||
false => sendToButton.GetText(),
|
||||
true => this.resultingContentBlock?.Content switch
|
||||
{
|
||||
ContentText textBlock => textBlock.Text,
|
||||
_ => string.Empty,
|
||||
},
|
||||
};
|
||||
|
||||
var (eventItem, path) = destination switch
|
||||
{
|
||||
SendTo.AGENDA_ASSISTANT => (Event.SEND_TO_AGENDA_ASSISTANT, Path.ASSISTANT_AGENDA),
|
||||
SendTo.CODING_ASSISTANT => (Event.SEND_TO_CODING_ASSISTANT, Path.ASSISTANT_CODING),
|
||||
SendTo.REWRITE_ASSISTANT => (Event.SEND_TO_REWRITE_ASSISTANT, Path.ASSISTANT_REWRITE),
|
||||
SendTo.TRANSLATION_ASSISTANT => (Event.SEND_TO_TRANSLATION_ASSISTANT, Path.ASSISTANT_TRANSLATION),
|
||||
SendTo.ICON_FINDER_ASSISTANT => (Event.SEND_TO_ICON_FINDER_ASSISTANT, Path.ASSISTANT_ICON_FINDER),
|
||||
SendTo.GRAMMAR_SPELLING_ASSISTANT => (Event.SEND_TO_GRAMMAR_SPELLING_ASSISTANT, Path.ASSISTANT_GRAMMAR_SPELLING),
|
||||
SendTo.TEXT_SUMMARIZER_ASSISTANT => (Event.SEND_TO_TEXT_SUMMARIZER_ASSISTANT, Path.ASSISTANT_SUMMARIZER),
|
||||
|
||||
SendTo.CHAT => (Event.SEND_TO_CHAT, Path.CHAT),
|
||||
|
||||
_ => (Event.NONE, Path.ASSISTANTS),
|
||||
};
|
||||
|
||||
switch (destination)
|
||||
{
|
||||
case SendTo.CHAT:
|
||||
MessageBus.INSTANCE.DeferMessage(this, eventItem, this.ConvertToChatThread);
|
||||
break;
|
||||
|
||||
default:
|
||||
MessageBus.INSTANCE.DeferMessage(this, eventItem, contentToSend);
|
||||
break;
|
||||
}
|
||||
|
||||
this.NavigationManager.NavigateTo(path);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task InnerResetForm()
|
||||
{
|
||||
this.resultingContentBlock = null;
|
||||
this.providerSettings = default;
|
||||
|
||||
await this.JsRuntime.ClearDiv(ASSISTANT_RESULT_DIV_ID);
|
||||
await this.JsRuntime.ClearDiv(AFTER_RESULT_DIV_ID);
|
||||
|
||||
this.ResetFrom();
|
||||
|
||||
this.inputIsValid = false;
|
||||
this.inputIssues = [];
|
||||
|
||||
this.form?.ResetValidation();
|
||||
this.StateHasChanged();
|
||||
this.form?.ResetValidation();
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
<MudCard Outlined="@true" Style="border-width: 2px; border-color: #0d47a1; border-radius: 12px; border-style: solid; max-width: 20em;">
|
||||
<MudCard Outlined="@true" Style="@this.BlockStyle">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudStack AlignItems="AlignItems.Center" Row="@true">
|
||||
@@ -0,0 +1,84 @@
|
||||
using AIStudio.Settings;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AIStudio.Components;
|
||||
|
||||
public partial class AssistantBlock : ComponentBase, IMessageBusReceiver, IDisposable
|
||||
{
|
||||
[Parameter]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
[Parameter]
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
[Parameter]
|
||||
public string Icon { get; set; } = Icons.Material.Filled.DisabledByDefault;
|
||||
|
||||
[Parameter]
|
||||
public string ButtonText { get; set; } = "Start";
|
||||
|
||||
[Parameter]
|
||||
public string Link { get; set; } = string.Empty;
|
||||
|
||||
[Inject]
|
||||
private MudTheme ColorTheme { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private SettingsManager SettingsManager { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private MessageBus MessageBus { get; init; } = null!;
|
||||
|
||||
#region Overrides of ComponentBase
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
this.MessageBus.RegisterComponent(this);
|
||||
this.MessageBus.ApplyFilters(this, [], [ Event.COLOR_THEME_CHANGED ]);
|
||||
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation of IMessageBusReceiver
|
||||
|
||||
public string ComponentName => nameof(AssistantBlock);
|
||||
|
||||
public Task ProcessMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data)
|
||||
{
|
||||
switch (triggeredEvent)
|
||||
{
|
||||
case Event.COLOR_THEME_CHANGED:
|
||||
this.StateHasChanged();
|
||||
break;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<TResult?> ProcessMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data)
|
||||
{
|
||||
return Task.FromResult<TResult?>(default);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private string BorderColor => this.SettingsManager.IsDarkMode switch
|
||||
{
|
||||
true => this.ColorTheme.GetCurrentPalette(this.SettingsManager).GrayLight,
|
||||
false => this.ColorTheme.GetCurrentPalette(this.SettingsManager).Primary.Value,
|
||||
};
|
||||
|
||||
private string BlockStyle => $"border-width: 2px; border-color: {this.BorderColor}; border-radius: 12px; border-style: solid; max-width: 20em;";
|
||||
|
||||
#region Implementation of IDisposable
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this.MessageBus.Unregister(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AIStudio.Components.Blocks;
|
||||
|
||||
public partial class AssistantBlock : ComponentBase
|
||||
{
|
||||
[Parameter]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
[Parameter]
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
[Parameter]
|
||||
public string Icon { get; set; } = Icons.Material.Filled.DisabledByDefault;
|
||||
|
||||
[Parameter]
|
||||
public string ButtonText { get; set; } = "Start";
|
||||
|
||||
[Parameter]
|
||||
public string Link { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
namespace AIStudio.Components.Blocks;
|
||||
|
||||
public partial class Changelog
|
||||
{
|
||||
public readonly record struct Log(int Build, string Display, string Filename)
|
||||
{
|
||||
#region Overrides of ValueType
|
||||
|
||||
public override string ToString() => this.Display;
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public static readonly Log[] LOGS =
|
||||
[
|
||||
new (172, "v0.8.10, build 172 (2024-08-18 19:44 UTC)", "v0.8.10.md"),
|
||||
new (171, "v0.8.9, build 171 (2024-08-18 10:35 UTC)", "v0.8.9.md"),
|
||||
new (170, "v0.8.8, build 170 (2024-08-14 06:30 UTC)", "v0.8.8.md"),
|
||||
new (169, "v0.8.7, build 169 (2024-08-01 19:08 UTC)", "v0.8.7.md"),
|
||||
new (168, "v0.8.6, build 168 (2024-08-01 19:50 UTC)", "v0.8.6.md"),
|
||||
new (167, "v0.8.5, build 167 (2024-07-28 16:44 UTC)", "v0.8.5.md"),
|
||||
new (166, "v0.8.4, build 166 (2024-07-26 06:53 UTC)", "v0.8.4.md"),
|
||||
new (165, "v0.8.3, build 165 (2024-07-25 13:25 UTC)", "v0.8.3.md"),
|
||||
new (164, "v0.8.2, build 164 (2024-07-16 18:03 UTC)", "v0.8.2.md"),
|
||||
new (163, "v0.8.1, build 163 (2024-07-16 08:32 UTC)", "v0.8.1.md"),
|
||||
new (162, "v0.8.0, build 162 (2024-07-14 19:39 UTC)", "v0.8.0.md"),
|
||||
new (161, "v0.7.1, build 161 (2024-07-13 11:42 UTC)", "v0.7.1.md"),
|
||||
new (160, "v0.7.0, build 160 (2024-07-13 08:21 UTC)", "v0.7.0.md"),
|
||||
new (159, "v0.6.3, build 159 (2024-07-03 18:26 UTC)", "v0.6.3.md"),
|
||||
new (158, "v0.6.2, build 158 (2024-07-01 18:03 UTC)", "v0.6.2.md"),
|
||||
new (157, "v0.6.1, build 157 (2024-06-30 19:00 UTC)", "v0.6.1.md"),
|
||||
new (156, "v0.6.0, build 156 (2024-06-30 12:49 UTC)", "v0.6.0.md"),
|
||||
new (155, "v0.5.2, build 155 (2024-06-25 18:07 UTC)", "v0.5.2.md"),
|
||||
new (154, "v0.5.1, build 154 (2024-06-25 15:35 UTC)", "v0.5.1.md"),
|
||||
new (149, "v0.5.0, build 149 (2024-06-02 18:51 UTC)", "v0.5.0.md"),
|
||||
new (138, "v0.4.0, build 138 (2024-05-26 13:26 UTC)", "v0.4.0.md"),
|
||||
new (120, "v0.3.0, build 120 (2024-05-18 21:57 UTC)", "v0.3.0.md"),
|
||||
new (90, "v0.2.0, build 90 (2024-05-04 10:50 UTC)", "v0.2.0.md"),
|
||||
new (45, "v0.1.0, build 45 (2024-04-20 21:27 UTC)", "v0.1.0.md"),
|
||||
|
||||
];
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<ConfigurationSelect OptionDescription="Preselected provider" Disabled="@this.Disabled" OptionHelp="Select a provider that is preselected." Data="@this.Data" SelectedValue="@this.SelectedValue" SelectionUpdate="@this.SelectionUpdate"/>
|
||||
@@ -1,11 +0,0 @@
|
||||
@inherits ConfigurationBase
|
||||
@typeparam T
|
||||
|
||||
<MudSelect T="T" Value="@this.SelectedValue()" Strict="@true" Disabled="@this.Disabled()" Margin="Margin.Dense" Label="@this.OptionDescription" Class="@GetClass" Variant="Variant.Outlined" HelperText="@this.OptionHelp" ValueChanged="@this.OptionChanged">
|
||||
@foreach (var data in this.Data)
|
||||
{
|
||||
<MudSelectItem Value="@data.Value">
|
||||
@data.Name
|
||||
</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
@@ -1,37 +0,0 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AIStudio.Components.Blocks;
|
||||
|
||||
public partial class ConfigurationText : ConfigurationBase
|
||||
{
|
||||
/// <summary>
|
||||
/// The text used for the textfield.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Func<string> Text { get; set; } = () => string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// An action which is called when the option is changed.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Action<string> TextUpdate { get; set; } = _ => { };
|
||||
|
||||
/// <summary>
|
||||
/// The icon to display next to the textfield.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string Icon { get; set; } = Icons.Material.Filled.Info;
|
||||
|
||||
/// <summary>
|
||||
/// The color of the icon to use.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Color IconColor { get; set; } = Color.Default;
|
||||
|
||||
private async Task OptionChanged(string updatedText)
|
||||
{
|
||||
this.TextUpdate(updatedText);
|
||||
await this.SettingsManager.StoreSettings();
|
||||
await this.InformAboutChange();
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
<MudExpansionPanel Class="border-solid border rounded-lg" Expanded="@this.IsExpanded">
|
||||
<TitleContent>
|
||||
<div class="d-flex align-center">
|
||||
<MudIcon Icon="@this.HeaderIcon" Size="@this.IconSize" Color="@this.IconColor" class="mr-3"/>
|
||||
<MudText Typo="Typo.h6">@this.HeaderText</MudText>
|
||||
</div>
|
||||
</TitleContent>
|
||||
<ChildContent>
|
||||
@this.ChildContent
|
||||
</ChildContent>
|
||||
</MudExpansionPanel>
|
||||
@@ -1,3 +0,0 @@
|
||||
namespace AIStudio.Components.Blocks;
|
||||
|
||||
public interface ITreeItem;
|
||||
@@ -1,9 +0,0 @@
|
||||
@inherits MSGComponentBase
|
||||
|
||||
<div class="d-flex flex-column" style="@this.Height">
|
||||
<div class="flex-auto overflow-auto">
|
||||
@this.ChildContent
|
||||
</div>
|
||||
|
||||
@this.FooterContent
|
||||
</div>
|
||||
@@ -1,62 +0,0 @@
|
||||
using AIStudio.Components.Layout;
|
||||
using AIStudio.Tools;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AIStudio.Components.Blocks;
|
||||
|
||||
public partial class InnerScrolling : MSGComponentBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Set the height of anything above the scrolling content; usually a header.
|
||||
/// What we do is calc(100vh - HeaderHeight). Means, you can use multiple measures like
|
||||
/// 230px - 3em. Default is 3em.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string HeaderHeight { get; set; } = "3em";
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment? ChildContent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional footer content, shown after the scrolling area.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public RenderFragment? FooterContent { get; set; }
|
||||
|
||||
[CascadingParameter]
|
||||
private MainLayout MainLayout { get; set; } = null!;
|
||||
|
||||
#region Overrides of ComponentBase
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
this.ApplyFilters([], [ Event.STATE_HAS_CHANGED ]);
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Overrides of MSGComponentBase
|
||||
|
||||
public override Task ProcessMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default
|
||||
{
|
||||
switch (triggeredEvent)
|
||||
{
|
||||
case Event.STATE_HAS_CHANGED:
|
||||
this.StateHasChanged();
|
||||
break;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public override Task<TResult?> ProcessMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data) where TResult : default where TPayload : default
|
||||
{
|
||||
return Task.FromResult(default(TResult));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private string Height => $"height: calc(100vh - {this.HeaderHeight} - {this.MainLayout.AdditionalHeight});";
|
||||
}
|
||||
Loaded 100 of 559 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user