mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-08-11 19:32:10 +00:00
Merge branch 'main' into assistant-builder-v2
This commit is contained in:
commit
a0065e628e
539
.github/workflows/build-and-release.yml
vendored
539
.github/workflows/build-and-release.yml
vendored
@ -19,6 +19,13 @@ concurrency:
|
||||
env:
|
||||
RETENTION_INTERMEDIATE_ASSETS: 1
|
||||
RETENTION_RELEASE_ASSETS: 30
|
||||
FLATPAK_REPOSITORY: MindWorkAI/Flatpak
|
||||
FLATPAK_WORKFLOW: flatpak.yml
|
||||
FLATPAK_YQ_VERSION: 4.44.6
|
||||
FLATPAK_YQ_SHA256: 0c2b24e645b57d8e7c0566d18643a6d4f5580feeea3878127354a46f2a1e4598
|
||||
FLATPAK_UV_VERSION: 0.11.28
|
||||
FLATPAK_UV_SHA256: e490a6464492183c5d4534a5527fb4440f7f2bb2f228162ad7e4afe076dc0224
|
||||
FLATPAK_FREEDESKTOP_VERSION: "25.08"
|
||||
|
||||
jobs:
|
||||
determine_run_mode:
|
||||
@ -165,6 +172,8 @@ jobs:
|
||||
formatted_build_time: ${{ steps.format_metadata.outputs.formatted_build_time }}
|
||||
changelog: ${{ steps.read_changelog.outputs.changelog }}
|
||||
version: ${{ steps.format_metadata.outputs.version }}
|
||||
source_commit: ${{ steps.format_metadata.outputs.source_commit }}
|
||||
pdfium_chromium_revision: ${{ steps.format_metadata.outputs.pdfium_chromium_revision }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
@ -176,6 +185,9 @@ jobs:
|
||||
# Read the first two lines of the metadata file:
|
||||
version=$(sed -n '1p' metadata.txt)
|
||||
build_time=$(sed -n '2p' metadata.txt)
|
||||
pdfium_full_version=$(sed -n '11p' metadata.txt)
|
||||
pdfium_chromium_revision=$(echo "$pdfium_full_version" | cut -d'.' -f3)
|
||||
source_commit=$(git rev-parse HEAD)
|
||||
|
||||
# Format the version:
|
||||
formatted_version="v${version}"
|
||||
@ -186,12 +198,16 @@ jobs:
|
||||
# Log the formatted metadata:
|
||||
echo "Formatted version: '${formatted_version}'"
|
||||
echo "Formatted build time: '${formatted_build_time}'"
|
||||
echo "Source commit: '${source_commit}'"
|
||||
echo "PDFium Chromium revision: '${pdfium_chromium_revision}'"
|
||||
|
||||
# Set the outputs:
|
||||
echo "formatted_version=${formatted_version}" >> "$GITHUB_OUTPUT"
|
||||
echo "FORMATTED_VERSION=${formatted_version}" >> $GITHUB_ENV
|
||||
echo "formatted_build_time=${formatted_build_time}" >> "$GITHUB_OUTPUT"
|
||||
echo "version=${version}" >> "$GITHUB_OUTPUT"
|
||||
echo "source_commit=${source_commit}" >> "$GITHUB_OUTPUT"
|
||||
echo "pdfium_chromium_revision=${pdfium_chromium_revision}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Check tag vs. metadata version
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
@ -224,6 +240,484 @@ jobs:
|
||||
echo "${changelog}" >> "$GITHUB_OUTPUT"
|
||||
echo "EOOOF" >> "$GITHUB_OUTPUT"
|
||||
|
||||
sync_flatpak_repo:
|
||||
name: Sync Flatpak repo
|
||||
runs-on: ubuntu-latest
|
||||
needs: [determine_run_mode, read_metadata]
|
||||
if: needs.determine_run_mode.outputs.is_release == 'true'
|
||||
permissions:
|
||||
contents: read
|
||||
outputs:
|
||||
flatpak_commit: ${{ steps.sync.outputs.flatpak_commit }}
|
||||
|
||||
env:
|
||||
AI_STUDIO_TAG: ${{ needs.read_metadata.outputs.formatted_version }}
|
||||
AI_STUDIO_COMMIT: ${{ needs.read_metadata.outputs.source_commit }}
|
||||
PDFIUM_CHROMIUM_REVISION: ${{ needs.read_metadata.outputs.pdfium_chromium_revision }}
|
||||
|
||||
steps:
|
||||
- name: Checkout Flatpak repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: ${{ env.FLATPAK_REPOSITORY }}
|
||||
token: ${{ secrets.FLATPAK_WORKFLOW_TOKEN }}
|
||||
ref: main
|
||||
path: flatpak
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install Flatpak sync tools and SDK
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
sudo apt-get update
|
||||
sudo apt-get install --no-install-recommends -y flatpak
|
||||
|
||||
flatpak remote-add \
|
||||
--user \
|
||||
--if-not-exists \
|
||||
flathub \
|
||||
https://flathub.org/repo/flathub.flatpakrepo
|
||||
flatpak install \
|
||||
--user \
|
||||
--noninteractive \
|
||||
-y \
|
||||
flathub \
|
||||
"org.freedesktop.Sdk//${FLATPAK_FREEDESKTOP_VERSION}" \
|
||||
"org.freedesktop.Sdk.Extension.dotnet9//${FLATPAK_FREEDESKTOP_VERSION}"
|
||||
|
||||
tools_dir="$RUNNER_TEMP/flatpak-sync-tools"
|
||||
mkdir -p "$tools_dir"
|
||||
|
||||
curl -fsSL \
|
||||
-o "$tools_dir/yq" \
|
||||
"https://github.com/mikefarah/yq/releases/download/v${FLATPAK_YQ_VERSION}/yq_linux_amd64"
|
||||
echo "${FLATPAK_YQ_SHA256} ${tools_dir}/yq" | sha256sum --check --strict
|
||||
chmod +x "$tools_dir/yq"
|
||||
|
||||
uv_archive="$RUNNER_TEMP/uv-x86_64-unknown-linux-gnu.tar.gz"
|
||||
curl -fsSL \
|
||||
-o "$uv_archive" \
|
||||
"https://github.com/astral-sh/uv/releases/download/${FLATPAK_UV_VERSION}/uv-x86_64-unknown-linux-gnu.tar.gz"
|
||||
echo "${FLATPAK_UV_SHA256} ${uv_archive}" | sha256sum --check --strict
|
||||
tar -xzf "$uv_archive" -C "$tools_dir"
|
||||
install -m 0755 "$tools_dir/uv-x86_64-unknown-linux-gnu/uv" "$tools_dir/uv"
|
||||
|
||||
echo "$tools_dir" >> "$GITHUB_PATH"
|
||||
"$tools_dir/uv" --version
|
||||
"$tools_dir/yq" --version
|
||||
|
||||
- name: Update Flatpak release sources
|
||||
working-directory: flatpak
|
||||
env:
|
||||
PDFIUM_X64_ARCHIVE: ${{ runner.temp }}/pdfium-linux-x64.tgz
|
||||
PDFIUM_ARM64_ARCHIVE: ${{ runner.temp }}/pdfium-linux-arm64.tgz
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
pdfium_base_url="https://github.com/bblanchon/pdfium-binaries/releases/download/chromium%2F${PDFIUM_CHROMIUM_REVISION}"
|
||||
pdfium_x64_url="${pdfium_base_url}/pdfium-linux-x64.tgz"
|
||||
pdfium_arm64_url="${pdfium_base_url}/pdfium-linux-arm64.tgz"
|
||||
|
||||
curl -fsSL -o "$PDFIUM_X64_ARCHIVE" "$pdfium_x64_url"
|
||||
curl -fsSL -o "$PDFIUM_ARM64_ARCHIVE" "$pdfium_arm64_url"
|
||||
|
||||
export PDFIUM_X64_URL="$pdfium_x64_url"
|
||||
export PDFIUM_ARM64_URL="$pdfium_arm64_url"
|
||||
export PDFIUM_X64_SHA256
|
||||
export PDFIUM_ARM64_SHA256
|
||||
PDFIUM_X64_SHA256=$(sha256sum "$PDFIUM_X64_ARCHIVE" | awk '{print $1}')
|
||||
PDFIUM_ARM64_SHA256=$(sha256sum "$PDFIUM_ARM64_ARCHIVE" | awk '{print $1}')
|
||||
|
||||
yq -i '
|
||||
(.modules[] | select(.name == "mind-work-ai-studio").sources[0].tag) = strenv(AI_STUDIO_TAG) |
|
||||
(.modules[] | select(.name == "mind-work-ai-studio").sources[0].commit) = strenv(AI_STUDIO_COMMIT) |
|
||||
(.modules[] | select(.name == "mind-work-ai-studio").sources[] | select(.type == "archive" and .only-arches[0] == "x86_64").url) = strenv(PDFIUM_X64_URL) |
|
||||
(.modules[] | select(.name == "mind-work-ai-studio").sources[] | select(.type == "archive" and .only-arches[0] == "x86_64").sha256) = strenv(PDFIUM_X64_SHA256) |
|
||||
(.modules[] | select(.name == "mind-work-ai-studio").sources[] | select(.type == "archive" and .only-arches[0] == "aarch64").url) = strenv(PDFIUM_ARM64_URL) |
|
||||
(.modules[] | select(.name == "mind-work-ai-studio").sources[] | select(.type == "archive" and .only-arches[0] == "aarch64").sha256) = strenv(PDFIUM_ARM64_SHA256)
|
||||
' org.MindWorkAI.AIStudio.yml
|
||||
|
||||
./update-dependencies
|
||||
|
||||
test "$(yq -r '(.modules[] | select(.name == "mind-work-ai-studio")).sources[0].tag' org.MindWorkAI.AIStudio.yml)" = "$AI_STUDIO_TAG"
|
||||
test "$(yq -r '(.modules[] | select(.name == "mind-work-ai-studio")).sources[0].commit' org.MindWorkAI.AIStudio.yml)" = "$AI_STUDIO_COMMIT"
|
||||
test "$(yq -r '(.modules[] | select(.name == "mind-work-ai-studio")).sources[] | select(.type == "archive" and .only-arches[0] == "x86_64").url' org.MindWorkAI.AIStudio.yml)" = "$PDFIUM_X64_URL"
|
||||
test "$(yq -r '(.modules[] | select(.name == "mind-work-ai-studio")).sources[] | select(.type == "archive" and .only-arches[0] == "x86_64").sha256' org.MindWorkAI.AIStudio.yml)" = "$PDFIUM_X64_SHA256"
|
||||
test "$(yq -r '(.modules[] | select(.name == "mind-work-ai-studio")).sources[] | select(.type == "archive" and .only-arches[0] == "aarch64").url' org.MindWorkAI.AIStudio.yml)" = "$PDFIUM_ARM64_URL"
|
||||
test "$(yq -r '(.modules[] | select(.name == "mind-work-ai-studio")).sources[] | select(.type == "archive" and .only-arches[0] == "aarch64").sha256' org.MindWorkAI.AIStudio.yml)" = "$PDFIUM_ARM64_SHA256"
|
||||
|
||||
for generated_source in cargo-sources.json dotnet-sources.json tauri-cli-sources.json; do
|
||||
test -s "$generated_source"
|
||||
jq -e 'type == "array" and length > 0' "$generated_source" > /dev/null
|
||||
done
|
||||
|
||||
git diff --check
|
||||
git diff --stat
|
||||
|
||||
- name: Commit and merge Flatpak sync
|
||||
id: sync
|
||||
working-directory: flatpak
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.FLATPAK_WORKFLOW_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
branch="sync/ai-studio-${AI_STUDIO_TAG}"
|
||||
git checkout -B "$branch"
|
||||
git add org.MindWorkAI.AIStudio.yml cargo-sources.json dotnet-sources.json tauri-cli-sources.json
|
||||
|
||||
if git diff --cached --quiet; then
|
||||
echo "Flatpak repository is already synced for ${AI_STUDIO_TAG}."
|
||||
flatpak_commit=$(git rev-parse HEAD)
|
||||
remote_main=$(git ls-remote origin refs/heads/main | awk '{print $1}')
|
||||
if [ "$remote_main" != "$flatpak_commit" ]; then
|
||||
echo "Flatpak main advanced from ${flatpak_commit} to ${remote_main} during synchronization."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "flatpak_commit=${flatpak_commit}" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
git commit -m "Sync AI Studio ${AI_STUDIO_TAG}"
|
||||
sync_commit=$(git rev-parse HEAD)
|
||||
|
||||
basic_auth=$(printf 'x-access-token:%s' "$GH_TOKEN" | base64 -w 0)
|
||||
git config --local http.https://github.com/.extraheader "AUTHORIZATION: basic ${basic_auth}"
|
||||
trap 'git config --local --unset-all http.https://github.com/.extraheader || true' EXIT
|
||||
|
||||
remote_branch_sha=$(git ls-remote --heads origin "$branch" | awk '{print $1}')
|
||||
if [ -n "$remote_branch_sha" ]; then
|
||||
git push --force-with-lease="refs/heads/${branch}:${remote_branch_sha}" origin "HEAD:${branch}"
|
||||
else
|
||||
git push origin "HEAD:${branch}"
|
||||
fi
|
||||
|
||||
pr_number=$(gh pr list \
|
||||
--repo "$FLATPAK_REPOSITORY" \
|
||||
--head "$branch" \
|
||||
--state open \
|
||||
--json number \
|
||||
--jq '.[0].number // empty')
|
||||
|
||||
if [ -z "$pr_number" ]; then
|
||||
pr_url=$(gh pr create \
|
||||
--repo "$FLATPAK_REPOSITORY" \
|
||||
--base main \
|
||||
--head "$branch" \
|
||||
--title "Sync AI Studio ${AI_STUDIO_TAG}" \
|
||||
--body "Synchronizes the Flatpak manifest and generated dependency sources for MindWork AI Studio ${AI_STUDIO_TAG}.")
|
||||
pr_number="${pr_url##*/}"
|
||||
fi
|
||||
|
||||
mergeable="UNKNOWN"
|
||||
for attempt in {1..30}; do
|
||||
pr_state=$(gh pr view "$pr_number" --repo "$FLATPAK_REPOSITORY" --json mergeable,mergeStateStatus)
|
||||
mergeable=$(echo "$pr_state" | jq -r '.mergeable')
|
||||
merge_state_status=$(echo "$pr_state" | jq -r '.mergeStateStatus')
|
||||
|
||||
echo "PR #${pr_number}: mergeable=${mergeable}, mergeStateStatus=${merge_state_status}"
|
||||
|
||||
if [ "$mergeable" = "MERGEABLE" ]; then
|
||||
break
|
||||
fi
|
||||
|
||||
if [ "$mergeable" = "CONFLICTING" ]; then
|
||||
echo "Flatpak sync PR #${pr_number} has merge conflicts."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sleep 5
|
||||
done
|
||||
|
||||
if [ "$mergeable" != "MERGEABLE" ]; then
|
||||
echo "Timed out waiting for Flatpak sync PR #${pr_number} to become mergeable."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
gh pr merge "$pr_number" \
|
||||
--repo "$FLATPAK_REPOSITORY" \
|
||||
--merge \
|
||||
--delete-branch \
|
||||
--match-head-commit "$sync_commit"
|
||||
|
||||
for attempt in {1..120}; do
|
||||
pr_state=$(gh pr view "$pr_number" \
|
||||
--repo "$FLATPAK_REPOSITORY" \
|
||||
--json state,mergedAt,mergeCommit)
|
||||
state=$(echo "$pr_state" | jq -r '.state')
|
||||
merged_at=$(echo "$pr_state" | jq -r '.mergedAt // empty')
|
||||
merge_commit=$(echo "$pr_state" | jq -r '.mergeCommit.oid // empty')
|
||||
|
||||
echo "PR #${pr_number}: state=${state}, mergedAt=${merged_at:-pending}, mergeCommit=${merge_commit:-pending}"
|
||||
|
||||
if [ -n "$merged_at" ] && [ -n "$merge_commit" ]; then
|
||||
echo "flatpak_commit=${merge_commit}" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$state" = "CLOSED" ]; then
|
||||
echo "Flatpak sync PR #${pr_number} was closed without being merged."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sleep 5
|
||||
done
|
||||
|
||||
echo "Timed out waiting for Flatpak sync PR #${pr_number} to be merged."
|
||||
exit 1
|
||||
|
||||
collect_flatpak_artifacts:
|
||||
name: Collect Flatpak artifacts
|
||||
runs-on: ubuntu-latest
|
||||
needs: [determine_run_mode, read_metadata, sync_flatpak_repo]
|
||||
if: needs.determine_run_mode.outputs.is_release == 'true'
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
FLATPAK_COMMIT: ${{ needs.sync_flatpak_repo.outputs.flatpak_commit }}
|
||||
|
||||
steps:
|
||||
- name: Wait for Flatpak main build
|
||||
id: flatpak_run
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.FLATPAK_WORKFLOW_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
find_run_id() {
|
||||
local created_after="${1:-}"
|
||||
local runs
|
||||
|
||||
runs=$(gh run list \
|
||||
--repo "$FLATPAK_REPOSITORY" \
|
||||
--workflow "$FLATPAK_WORKFLOW" \
|
||||
--branch main \
|
||||
--commit "$FLATPAK_COMMIT" \
|
||||
--limit 20 \
|
||||
--json databaseId,event,headSha,createdAt)
|
||||
|
||||
if [ -n "$created_after" ]; then
|
||||
echo "$runs" | jq -r \
|
||||
--arg commit "$FLATPAK_COMMIT" \
|
||||
--arg created_after "$created_after" \
|
||||
'[.[] | select(.headSha == $commit and .event == "workflow_dispatch" and .createdAt >= $created_after)][0].databaseId // empty'
|
||||
else
|
||||
echo "$runs" | jq -r \
|
||||
--arg commit "$FLATPAK_COMMIT" \
|
||||
'[.[] | select(.headSha == $commit and (.event == "push" or .event == "workflow_dispatch"))][0].databaseId // empty'
|
||||
fi
|
||||
}
|
||||
|
||||
validate_required_artifacts() {
|
||||
local run_id="$1"
|
||||
local artifacts
|
||||
local expected_name
|
||||
local match_count
|
||||
local custom_count
|
||||
|
||||
artifacts=$(gh api "repos/${FLATPAK_REPOSITORY}/actions/runs/${run_id}/artifacts?per_page=100")
|
||||
custom_count=$(echo "$artifacts" | jq \
|
||||
'[.artifacts[] | select(.name | startswith("MindWork AI Studio Flatpak ("))] | length')
|
||||
|
||||
if [ "$custom_count" -ne 2 ]; then
|
||||
echo "Flatpak run ${run_id} contains ${custom_count} release artifacts; expected 2."
|
||||
return 1
|
||||
fi
|
||||
|
||||
for expected_name in \
|
||||
"MindWork AI Studio Flatpak (x86_64)" \
|
||||
"MindWork AI Studio Flatpak (aarch64)"; do
|
||||
match_count=$(echo "$artifacts" | jq \
|
||||
--arg name "$expected_name" \
|
||||
'[.artifacts[] | select(.name == $name and .expired == false)] | length')
|
||||
|
||||
if [ "$match_count" -ne 1 ]; then
|
||||
echo "Flatpak run ${run_id} does not contain one active '${expected_name}' artifact."
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
wait_for_run() {
|
||||
local run_id="$1"
|
||||
local run_json
|
||||
local status
|
||||
local conclusion
|
||||
local head_sha
|
||||
local url
|
||||
|
||||
for attempt in {1..180}; do
|
||||
run_json=$(gh run view "$run_id" \
|
||||
--repo "$FLATPAK_REPOSITORY" \
|
||||
--json status,conclusion,headSha,url)
|
||||
status=$(echo "$run_json" | jq -r '.status')
|
||||
conclusion=$(echo "$run_json" | jq -r '.conclusion // empty')
|
||||
head_sha=$(echo "$run_json" | jq -r '.headSha')
|
||||
url=$(echo "$run_json" | jq -r '.url')
|
||||
|
||||
echo "Flatpak run ${run_id}: status=${status}, conclusion=${conclusion:-pending}, url=${url}"
|
||||
|
||||
if [ "$head_sha" != "$FLATPAK_COMMIT" ]; then
|
||||
echo "Flatpak run ${run_id} targets '${head_sha}', expected '${FLATPAK_COMMIT}'."
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ "$status" = "completed" ]; then
|
||||
if [ "$conclusion" = "success" ] && validate_required_artifacts "$run_id"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "Flatpak run ${run_id} completed without usable release artifacts."
|
||||
return 1
|
||||
fi
|
||||
|
||||
sleep 20
|
||||
done
|
||||
|
||||
echo "Timed out waiting for Flatpak run ${run_id}."
|
||||
return 2
|
||||
}
|
||||
|
||||
run_id=""
|
||||
for attempt in {1..15}; do
|
||||
run_id=$(find_run_id)
|
||||
if [ -n "$run_id" ]; then
|
||||
break
|
||||
fi
|
||||
|
||||
echo "Waiting for Flatpak workflow on commit ${FLATPAK_COMMIT}..."
|
||||
sleep 20
|
||||
done
|
||||
|
||||
if [ -z "$run_id" ]; then
|
||||
current_main=$(gh api "repos/${FLATPAK_REPOSITORY}/commits/main" --jq .sha)
|
||||
if [ "$current_main" != "$FLATPAK_COMMIT" ]; then
|
||||
echo "No Flatpak run exists for ${FLATPAK_COMMIT}, and Flatpak main has advanced to ${current_main}."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
dispatch_started_at=$(date --utc +'%Y-%m-%dT%H:%M:%SZ')
|
||||
gh workflow run "$FLATPAK_WORKFLOW" \
|
||||
--repo "$FLATPAK_REPOSITORY" \
|
||||
--ref main \
|
||||
-f "artifact_retention_days=${RETENTION_INTERMEDIATE_ASSETS}"
|
||||
|
||||
for attempt in {1..15}; do
|
||||
run_id=$(find_run_id "$dispatch_started_at")
|
||||
if [ -n "$run_id" ]; then
|
||||
break
|
||||
fi
|
||||
|
||||
echo "Waiting for the dispatched Flatpak workflow on commit ${FLATPAK_COMMIT}..."
|
||||
sleep 20
|
||||
done
|
||||
fi
|
||||
|
||||
if [ -z "$run_id" ]; then
|
||||
echo "Timed out waiting for a Flatpak workflow to start on commit ${FLATPAK_COMMIT}."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set +e
|
||||
wait_for_run "$run_id"
|
||||
wait_result=$?
|
||||
set -e
|
||||
|
||||
if [ "$wait_result" -eq 0 ]; then
|
||||
echo "run_id=${run_id}" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$wait_result" -eq 2 ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Re-running Flatpak run ${run_id} once."
|
||||
previous_attempt=$(gh api "repos/${FLATPAK_REPOSITORY}/actions/runs/${run_id}" --jq .run_attempt)
|
||||
gh run rerun "$run_id" --repo "$FLATPAK_REPOSITORY"
|
||||
|
||||
rerun_started=false
|
||||
for attempt in {1..60}; do
|
||||
rerun_state=$(gh api "repos/${FLATPAK_REPOSITORY}/actions/runs/${run_id}")
|
||||
current_attempt=$(echo "$rerun_state" | jq -r '.run_attempt')
|
||||
current_status=$(echo "$rerun_state" | jq -r '.status')
|
||||
|
||||
if [ "$current_attempt" -gt "$previous_attempt" ] || [ "$current_status" != "completed" ]; then
|
||||
rerun_started=true
|
||||
break
|
||||
fi
|
||||
|
||||
sleep 2
|
||||
done
|
||||
|
||||
if [ "$rerun_started" != "true" ]; then
|
||||
echo "Timed out waiting for Flatpak run ${run_id} to start its retry."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! wait_for_run "$run_id"; then
|
||||
echo "Flatpak run ${run_id} did not succeed after one retry."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "run_id=${run_id}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Download Flatpak artifacts
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.FLATPAK_WORKFLOW_TOKEN }}
|
||||
RUN_ID: ${{ steps.flatpak_run.outputs.run_id }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
mkdir -p flatpak-artifacts
|
||||
gh run download "$RUN_ID" \
|
||||
--repo "$FLATPAK_REPOSITORY" \
|
||||
--name "MindWork AI Studio Flatpak (x86_64)" \
|
||||
--name "MindWork AI Studio Flatpak (aarch64)" \
|
||||
--dir flatpak-artifacts
|
||||
|
||||
expected_files=(
|
||||
"MindWork AI Studio_x86_64.flatpak"
|
||||
"MindWork AI Studio Plugin Pandoc_x86_64.flatpak"
|
||||
"MindWork AI Studio_aarch64.flatpak"
|
||||
"MindWork AI Studio Plugin Pandoc_aarch64.flatpak"
|
||||
)
|
||||
|
||||
flatpak_count=$(find flatpak-artifacts -type f -name '*.flatpak' | wc -l | tr -d ' ')
|
||||
if [ "$flatpak_count" -ne 4 ]; then
|
||||
echo "Downloaded ${flatpak_count} Flatpak files; expected 4."
|
||||
find flatpak-artifacts -type f -print
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for expected_file in "${expected_files[@]}"; do
|
||||
match_count=$(find flatpak-artifacts -type f -name "$expected_file" | wc -l | tr -d ' ')
|
||||
if [ "$match_count" -ne 1 ]; then
|
||||
echo "Expected exactly one '${expected_file}', found ${match_count}."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
matched_file=$(find flatpak-artifacts -type f -name "$expected_file" -print -quit)
|
||||
test -s "$matched_file"
|
||||
done
|
||||
|
||||
find flatpak-artifacts -type f -name '*.flatpak' -print
|
||||
|
||||
- name: Upload Flatpak artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: MindWork AI Studio Flatpak Release
|
||||
path: flatpak-artifacts/**/*.flatpak
|
||||
if-no-files-found: error
|
||||
overwrite: true
|
||||
retention-days: ${{ env.RETENTION_INTERMEDIATE_ASSETS }}
|
||||
|
||||
build_main:
|
||||
name: Build app (${{ matrix.dotnet_runtime }})
|
||||
needs: [determine_run_mode, read_metadata]
|
||||
@ -789,7 +1283,7 @@ jobs:
|
||||
create_release:
|
||||
name: Prepare & create release
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build_main, read_metadata]
|
||||
needs: [build_main, collect_flatpak_artifacts, read_metadata]
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
permissions: {}
|
||||
steps:
|
||||
@ -808,16 +1302,19 @@ jobs:
|
||||
- name: Prepare release assets
|
||||
env:
|
||||
VERSION: ${{ needs.read_metadata.outputs.version }}
|
||||
|
||||
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
RELEASE_DIR="$GITHUB_WORKSPACE/release/assets"
|
||||
|
||||
declare -A release_asset_sources
|
||||
|
||||
# Ensure the release directory exists:
|
||||
mkdir -p "$RELEASE_DIR"
|
||||
|
||||
|
||||
# Find and process files in the artifacts directory:
|
||||
find "$GITHUB_WORKSPACE/artifacts" -type f | while read -r FILE; do
|
||||
|
||||
while IFS= read -r -d '' FILE; do
|
||||
|
||||
if [[ "$FILE" == *"osx-x64"* && "$FILE" == *".tar.gz.sig" ]]; then
|
||||
TARGET_NAME="MindWork AI Studio_x64.app.tar.gz.sig"
|
||||
elif [[ "$FILE" == *"osx-x64"* && "$FILE" == *".tar.gz" ]]; then
|
||||
@ -830,10 +1327,36 @@ jobs:
|
||||
TARGET_NAME="$(basename "$FILE")"
|
||||
TARGET_NAME=$(echo "$TARGET_NAME" | sed "s/_${VERSION}//")
|
||||
fi
|
||||
|
||||
|
||||
if [ -n "${release_asset_sources[$TARGET_NAME]+x}" ]; then
|
||||
echo "Duplicate release asset name '${TARGET_NAME}':"
|
||||
echo " ${release_asset_sources[$TARGET_NAME]}"
|
||||
echo " ${FILE}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
release_asset_sources[$TARGET_NAME]="$FILE"
|
||||
|
||||
cp "$FILE" "${RELEASE_DIR}/${TARGET_NAME}"
|
||||
done < <(find "$GITHUB_WORKSPACE/artifacts" -type f -print0)
|
||||
|
||||
expected_flatpaks=(
|
||||
"MindWork AI Studio_x86_64.flatpak"
|
||||
"MindWork AI Studio Plugin Pandoc_x86_64.flatpak"
|
||||
"MindWork AI Studio_aarch64.flatpak"
|
||||
"MindWork AI Studio Plugin Pandoc_aarch64.flatpak"
|
||||
)
|
||||
|
||||
flatpak_count=$(find "$RELEASE_DIR" -maxdepth 1 -type f -name '*.flatpak' | wc -l | tr -d ' ')
|
||||
if [ "$flatpak_count" -ne 4 ]; then
|
||||
echo "Prepared ${flatpak_count} Flatpak release assets; expected 4."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for expected_flatpak in "${expected_flatpaks[@]}"; do
|
||||
test -s "${RELEASE_DIR}/${expected_flatpak}"
|
||||
done
|
||||
|
||||
|
||||
# Display the structure of the release directory:
|
||||
ls -Rlhat $GITHUB_WORKSPACE/release/assets
|
||||
|
||||
|
||||
@ -21,6 +21,9 @@
|
||||
|
||||
<body style="overflow: hidden;">
|
||||
<Routes @rendermode="new InteractiveServerRenderMode(prerender: false)"/>
|
||||
<div id="reconnect-modal" style="display: none; position: fixed; inset: 0; z-index: 20000; align-items: center; justify-content: center; padding: 2rem; background: rgba(15, 23, 42, 0.82); color: white; font-size: 1.1rem; text-align: center;">
|
||||
Reconnecting to AI Studio...
|
||||
</div>
|
||||
<script src="_framework/blazor.web.js" autostart="false"></script>
|
||||
<script src="boot.js"></script>
|
||||
<script src="system/MudBlazor/MudBlazor.min.js"></script>
|
||||
|
||||
@ -1600,23 +1600,29 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LEGALCHECK::ASSISTANTLEGALCHECK::T4254597
|
||||
-- Ask your questions
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LEGALCHECK::ASSISTANTLEGALCHECK::T467099852"] = "Ask your questions"
|
||||
|
||||
-- Analyze the following text and extract my tasks:
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T1349891364"] = "Analyze the following text and extract my tasks:"
|
||||
-- You can enter text, attach one or more documents, or use both. At least one input is required.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T1442535450"] = "You can enter text, attach one or more documents, or use both. At least one input is required."
|
||||
|
||||
-- Please provide some text as input. For example, an email.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T1962809521"] = "Please provide some text as input. For example, an email."
|
||||
-- Please provide some text or at least one valid document as input. For example, an email.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T1635845349"] = "Please provide some text or at least one valid document as input. For example, an email."
|
||||
|
||||
-- Analyze text
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T2268303626"] = "Analyze text"
|
||||
-- You received a cryptic email or document that was sent to many recipients and you are now wondering if you need to do something? Copy the text into the input field, attach one or more documents, or use both. 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.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T1918551346"] = "You received a cryptic email or document that was sent to many recipients and you are now wondering if you need to do something? Copy the text into the input field, attach one or more documents, or use both. 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."
|
||||
|
||||
-- Target language
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T237828418"] = "Target language"
|
||||
|
||||
-- Analyze the following text and/or attached documents and extract my tasks:
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T2535924263"] = "Analyze the following text and/or attached documents and extract my tasks:"
|
||||
|
||||
-- My Tasks
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T3011450657"] = "My Tasks"
|
||||
|
||||
-- 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.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T3646084045"] = "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."
|
||||
-- Analyze content
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T3334965934"] = "Analyze content"
|
||||
|
||||
-- Attach documents
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T3666048746"] = "Attach documents"
|
||||
|
||||
-- Custom target language
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T3848935911"] = "Custom target language"
|
||||
|
||||
@ -3,5 +3,12 @@
|
||||
|
||||
<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="@T("Text or email")" Variant="Variant.Outlined" Lines="12" AutoGrow="@true" MaxLines="24" Class="mb-3" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
|
||||
<MudText Typo="Typo.h6" Class="mb-1 mt-1">@T("Attach documents")</MudText>
|
||||
<MudJustifiedText Typo="Typo.body1" Class="mb-2">
|
||||
@T("You can enter text, attach one or more documents, or use both. At least one input is required.")
|
||||
</MudJustifiedText>
|
||||
<div class="mb-3">
|
||||
<AttachDocuments Name="My Tasks Documents" Layer="@DropLayers.ASSISTANTS" @bind-DocumentPaths="@this.loadedDocumentPaths" OnChange="@this.OnDocumentsChanged" CatchAllDocuments="true" UseSmallForm="false" Provider="@this.ProviderSettings"/>
|
||||
</div>
|
||||
<EnumSelection T="CommonLanguages" NameFunc="@(language => language.NameSelectingOptional())" @bind-Value="@this.selectedTargetLanguage" Icon="@Icons.Material.Filled.Translate" Label="@T("Target language")" AllowOther="@true" OtherValue="CommonLanguages.OTHER" @bind-OtherInput="@this.customTargetLanguage" ValidateOther="@this.ValidateCustomLanguage" LabelOther="@T("Custom target language")" />
|
||||
<ProviderSelection @bind-ProviderSettings="@this.ProviderSettings" ValidateProvider="@this.ValidatingProvider"/>
|
||||
@ -1,3 +1,4 @@
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Dialogs.Settings;
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Tools.AssistantSessions;
|
||||
@ -10,34 +11,84 @@ public partial class AssistantMyTasks : AssistantBaseCore<SettingsDialogMyTasks>
|
||||
|
||||
protected override string Title => T("My Tasks");
|
||||
|
||||
protected override string Description => T("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 Description => T("You received a cryptic email or document that was sent to many recipients and you are now wondering if you need to do something? Copy the text into the input field, attach one or more documents, or use both. 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.
|
||||
reports, etc. as text input and/or attached documents. 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
|
||||
provided content. 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 => T("Analyze text");
|
||||
protected override string SubmitText => T("Analyze content");
|
||||
|
||||
protected override Func<Task> SubmitAction => this.AnalyzeText;
|
||||
|
||||
protected override bool ShowProfileSelection => false;
|
||||
|
||||
protected override string SendToChatVisibleUserPromptPrefix => T("Analyze the following text and extract my tasks:");
|
||||
protected override string SendToChatVisibleUserPromptPrefix => T("Analyze the following text and/or attached documents and extract my tasks:");
|
||||
|
||||
protected override string SendToChatVisibleUserPromptContent => this.inputText;
|
||||
|
||||
protected override ChatThread ConvertToChatThread
|
||||
{
|
||||
get
|
||||
{
|
||||
var originalChatThread = this.ChatThread ?? new ChatThread();
|
||||
if (string.IsNullOrWhiteSpace(this.SendToChatVisibleUserPromptText))
|
||||
{
|
||||
return originalChatThread with
|
||||
{
|
||||
SystemPrompt = SystemPrompts.DEFAULT,
|
||||
};
|
||||
}
|
||||
|
||||
var earliestBlock = originalChatThread.Blocks.MinBy(x => x.Time);
|
||||
var visiblePromptTime = earliestBlock is null
|
||||
? DateTimeOffset.Now
|
||||
: earliestBlock.Time == DateTimeOffset.MinValue
|
||||
? earliestBlock.Time
|
||||
: earliestBlock.Time.AddTicks(-1);
|
||||
|
||||
var transferredBlocks = originalChatThread.Blocks
|
||||
.Select(block => block.Role is ChatRole.USER
|
||||
? this.CloneHiddenUserBlockWithoutAttachments(block)
|
||||
: block.DeepClone())
|
||||
.ToList();
|
||||
|
||||
transferredBlocks.Insert(0, new ContentBlock
|
||||
{
|
||||
Time = visiblePromptTime,
|
||||
ContentType = ContentType.TEXT,
|
||||
HideFromUser = false,
|
||||
Role = ChatRole.USER,
|
||||
Content = new ContentText
|
||||
{
|
||||
Text = this.SendToChatVisibleUserPromptText,
|
||||
FileAttachments = this.loadedDocumentPaths.ToList(),
|
||||
},
|
||||
});
|
||||
|
||||
return originalChatThread with
|
||||
{
|
||||
ChatId = Guid.NewGuid(),
|
||||
SystemPrompt = SystemPrompts.DEFAULT,
|
||||
Blocks = transferredBlocks,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ResetForm()
|
||||
{
|
||||
this.inputText = string.Empty;
|
||||
this.loadedDocumentPaths.Clear();
|
||||
if (!this.MightPreselectValues())
|
||||
{
|
||||
this.selectedTargetLanguage = CommonLanguages.AS_IS;
|
||||
@ -58,9 +109,11 @@ public partial class AssistantMyTasks : AssistantBaseCore<SettingsDialogMyTasks>
|
||||
}
|
||||
|
||||
private string inputText = string.Empty;
|
||||
private HashSet<FileAttachment> loadedDocumentPaths = [];
|
||||
private CommonLanguages selectedTargetLanguage = CommonLanguages.AS_IS;
|
||||
private string customTargetLanguage = string.Empty;
|
||||
private static readonly AssistantSessionStateKey<string> INPUT_TEXT_STATE_KEY = new(nameof(inputText));
|
||||
private static readonly AssistantSessionStateKey<HashSet<FileAttachment>> LOADED_DOCUMENT_PATHS_STATE_KEY = new(nameof(loadedDocumentPaths));
|
||||
private static readonly AssistantSessionStateKey<CommonLanguages> SELECTED_TARGET_LANGUAGE_STATE_KEY = new(nameof(selectedTargetLanguage));
|
||||
private static readonly AssistantSessionStateKey<string> CUSTOM_TARGET_LANGUAGE_STATE_KEY = new(nameof(customTargetLanguage));
|
||||
|
||||
@ -68,6 +121,7 @@ public partial class AssistantMyTasks : AssistantBaseCore<SettingsDialogMyTasks>
|
||||
protected override void CaptureCustomAssistantSessionState(AssistantSessionStateWriter state)
|
||||
{
|
||||
state.Set(INPUT_TEXT_STATE_KEY, this.inputText);
|
||||
state.SetHashSet(LOADED_DOCUMENT_PATHS_STATE_KEY, this.loadedDocumentPaths);
|
||||
state.Set(SELECTED_TARGET_LANGUAGE_STATE_KEY, this.selectedTargetLanguage);
|
||||
state.Set(CUSTOM_TARGET_LANGUAGE_STATE_KEY, this.customTargetLanguage);
|
||||
}
|
||||
@ -76,6 +130,7 @@ public partial class AssistantMyTasks : AssistantBaseCore<SettingsDialogMyTasks>
|
||||
protected override void RestoreCustomAssistantSessionState(AssistantSessionStateReader state)
|
||||
{
|
||||
state.Restore(INPUT_TEXT_STATE_KEY, value => this.inputText = value);
|
||||
state.RestoreHashSet(LOADED_DOCUMENT_PATHS_STATE_KEY, this.loadedDocumentPaths);
|
||||
state.Restore(SELECTED_TARGET_LANGUAGE_STATE_KEY, value => this.selectedTargetLanguage = value);
|
||||
state.Restore(CUSTOM_TARGET_LANGUAGE_STATE_KEY, value => this.customTargetLanguage = value);
|
||||
}
|
||||
@ -95,12 +150,20 @@ public partial class AssistantMyTasks : AssistantBaseCore<SettingsDialogMyTasks>
|
||||
|
||||
private string? ValidatingText(string text)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(text))
|
||||
return T("Please provide some text as input. For example, an email.");
|
||||
if(string.IsNullOrWhiteSpace(text) && !this.HasValidInputDocuments())
|
||||
return T("Please provide some text or at least one valid document as input. For example, an email.");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool HasValidInputDocuments() => this.loadedDocumentPaths.Any(n => n is { Exists: true, IsValid: true });
|
||||
|
||||
private async Task OnDocumentsChanged(HashSet<FileAttachment> _)
|
||||
{
|
||||
if(this.Form is not null)
|
||||
await this.Form.Validate();
|
||||
}
|
||||
|
||||
private string? ValidateProfile(Profile profile)
|
||||
{
|
||||
if(profile == Profile.NO_PROFILE)
|
||||
@ -127,6 +190,23 @@ public partial class AssistantMyTasks : AssistantBaseCore<SettingsDialogMyTasks>
|
||||
|
||||
return this.selectedTargetLanguage.Name();
|
||||
}
|
||||
|
||||
private ContentBlock CloneHiddenUserBlockWithoutAttachments(ContentBlock block)
|
||||
{
|
||||
var clone = block.DeepClone(changeHideState: true);
|
||||
if (clone.Content is ContentText text)
|
||||
text.FileAttachments = [];
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
private string BuildUserRequest()
|
||||
{
|
||||
if(!string.IsNullOrWhiteSpace(this.inputText))
|
||||
return this.inputText;
|
||||
|
||||
return "Analyze the attached document(s) and extract my tasks.";
|
||||
}
|
||||
|
||||
private async Task AnalyzeText()
|
||||
{
|
||||
@ -135,7 +215,7 @@ public partial class AssistantMyTasks : AssistantBaseCore<SettingsDialogMyTasks>
|
||||
return;
|
||||
|
||||
this.CreateChatThread();
|
||||
var time = this.AddUserRequest(this.inputText);
|
||||
var time = this.AddUserRequest(this.BuildUserRequest(), false, this.loadedDocumentPaths.ToList());
|
||||
|
||||
await this.AddAIResponseAsync(time);
|
||||
}
|
||||
|
||||
@ -1602,23 +1602,29 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LEGALCHECK::ASSISTANTLEGALCHECK::T4254597
|
||||
-- Ask your questions
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LEGALCHECK::ASSISTANTLEGALCHECK::T467099852"] = "Stellen Sie ihre Fragen"
|
||||
|
||||
-- Analyze the following text and extract my tasks:
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T1349891364"] = "Analysiere den folgenden Text und extrahiere meine Aufgaben:"
|
||||
-- You can enter text, attach one or more documents, or use both. At least one input is required.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T1442535450"] = "Sie können Text eingeben, ein oder mehrere Dokumente anhängen oder beides verwenden. Mindestens eine Eingabe ist erforderlich."
|
||||
|
||||
-- Please provide some text as input. For example, an email.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T1962809521"] = "Bitte geben Sie einen Text ein. Zum Beispiel eine E-Mail."
|
||||
-- Please provide some text or at least one valid document as input. For example, an email.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T1635845349"] = "Bitte geben Sie einen Text oder mindestens ein gültiges Dokument als Eingabe an. Zum Beispiel eine E-Mail."
|
||||
|
||||
-- Analyze text
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T2268303626"] = "Text analysieren"
|
||||
-- You received a cryptic email or document that was sent to many recipients and you are now wondering if you need to do something? Copy the text into the input field, attach one or more documents, or use both. 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.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T1918551346"] = "Sie haben eine kryptische E-Mail oder ein Dokument erhalten, das an viele Empfänger gesendet wurde, und fragen sich nun, ob Sie etwas tun müssen? Kopieren Sie den Text in das Eingabefeld, fügen Sie ein oder mehrere Dokumente an oder nutzen Sie beides. Außerdem müssen Sie ein persönliches Profil auswählen. In diesem Profil sollten Sie Ihre Rolle in der Organisation beschreiben. Die KI wird dann versuchen, Ihnen Hinweise darauf zu geben, welche Aufgaben Sie möglicherweise haben."
|
||||
|
||||
-- Target language
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T237828418"] = "Zielsprache"
|
||||
|
||||
-- Analyze the following text and/or attached documents and extract my tasks:
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T2535924263"] = "Analysiere den folgenden Text und/oder die angehängten Dokumente und extrahiere meine Aufgaben:"
|
||||
|
||||
-- My Tasks
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T3011450657"] = "Meine Aufgaben"
|
||||
|
||||
-- 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.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T3646084045"] = "Sie haben eine rätselhafte E-Mail erhalten, die an viele Empfänger verschickt wurde, und fragen sich nun, ob Sie etwas unternehmen müssen? Kopieren Sie die E-Mail in das Eingabefeld. Außerdem müssen Sie ein persönliches Profil auswählen. In diesem Profil sollten Sie ihre Rolle in der Organisation beschreiben. Die KI wird Ihnen dann Hinweise geben, welche Aufgaben für Sie daraus entstehen könnten."
|
||||
-- Analyze content
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T3334965934"] = "Inhalt analysieren"
|
||||
|
||||
-- Attach documents
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T3666048746"] = "Dokumente anhängen"
|
||||
|
||||
-- Custom target language
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T3848935911"] = "Benutzerdefinierte Zielsprache"
|
||||
|
||||
@ -1602,23 +1602,29 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LEGALCHECK::ASSISTANTLEGALCHECK::T4254597
|
||||
-- Ask your questions
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LEGALCHECK::ASSISTANTLEGALCHECK::T467099852"] = "Ask your questions"
|
||||
|
||||
-- Analyze the following text and extract my tasks:
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T1349891364"] = "Analyze the following text and extract my tasks:"
|
||||
-- You can enter text, attach one or more documents, or use both. At least one input is required.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T1442535450"] = "You can enter text, attach one or more documents, or use both. At least one input is required."
|
||||
|
||||
-- Please provide some text as input. For example, an email.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T1962809521"] = "Please provide some text as input. For example, an email."
|
||||
-- Please provide some text or at least one valid document as input. For example, an email.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T1635845349"] = "Please provide some text or at least one valid document as input. For example, an email."
|
||||
|
||||
-- Analyze text
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T2268303626"] = "Analyze text"
|
||||
-- You received a cryptic email or document that was sent to many recipients and you are now wondering if you need to do something? Copy the text into the input field, attach one or more documents, or use both. 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.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T1918551346"] = "You received a cryptic email or document that was sent to many recipients and you are now wondering if you need to do something? Copy the text into the input field, attach one or more documents, or use both. 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."
|
||||
|
||||
-- Target language
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T237828418"] = "Target language"
|
||||
|
||||
-- Analyze the following text and/or attached documents and extract my tasks:
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T2535924263"] = "Analyze the following text and/or attached documents and extract my tasks:"
|
||||
|
||||
-- My Tasks
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T3011450657"] = "My Tasks"
|
||||
|
||||
-- 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.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T3646084045"] = "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."
|
||||
-- Analyze content
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T3334965934"] = "Analyze content"
|
||||
|
||||
-- Attach documents
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T3666048746"] = "Attach documents"
|
||||
|
||||
-- Custom target language
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T3848935911"] = "Custom target language"
|
||||
|
||||
@ -155,12 +155,17 @@ internal sealed class Program
|
||||
// ReSharper restore AccessToDisposedClosure
|
||||
|
||||
builder.Services.AddRazorComponents()
|
||||
.AddInteractiveServerComponents()
|
||||
.AddInteractiveServerComponents(options =>
|
||||
{
|
||||
options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromDays(30);
|
||||
options.DisconnectedCircuitMaxRetained = 2;
|
||||
})
|
||||
.AddHubOptions(options =>
|
||||
{
|
||||
options.MaximumReceiveMessageSize = null;
|
||||
options.ClientTimeoutInterval = TimeSpan.FromDays(14);
|
||||
options.ClientTimeoutInterval = TimeSpan.FromSeconds(120);
|
||||
options.HandshakeTimeout = TimeSpan.FromSeconds(30);
|
||||
options.KeepAliveInterval = TimeSpan.FromSeconds(30);
|
||||
});
|
||||
|
||||
builder.Services.AddSingleton(new HttpClient
|
||||
|
||||
@ -65,7 +65,7 @@ public static partial class PluginFactory
|
||||
await response.Content.CopyToAsync(tempFileStream, cancellationToken);
|
||||
}
|
||||
|
||||
ZipFile.ExtractToDirectory(tempDownloadFile, stagedDirectory);
|
||||
ExtractConfigPluginArchive(tempDownloadFile, stagedDirectory);
|
||||
|
||||
var configDirectory = Path.Join(CONFIGURATION_PLUGINS_ROOT, configPlugId.ToString());
|
||||
if (Directory.Exists(configDirectory))
|
||||
@ -129,4 +129,70 @@ public static partial class PluginFactory
|
||||
|
||||
return wasSuccessful;
|
||||
}
|
||||
|
||||
// Compatibility shim for Windows-created ZIPs with backslashes in entry names (dotnet/runtime#27620).
|
||||
// See documentation/compatibility-shims/2026-07-enterprise-config-zip-backslashes.md.
|
||||
private static void ExtractConfigPluginArchive(string sourceArchiveFileName, string destinationDirectory)
|
||||
{
|
||||
using var archive = ZipFile.OpenRead(sourceArchiveFileName);
|
||||
Directory.CreateDirectory(destinationDirectory);
|
||||
|
||||
var destinationDirectoryFullPath = Path.GetFullPath(destinationDirectory);
|
||||
if (!destinationDirectoryFullPath.EndsWith(Path.DirectorySeparatorChar))
|
||||
destinationDirectoryFullPath += Path.DirectorySeparatorChar;
|
||||
|
||||
foreach (var entry in archive.Entries)
|
||||
{
|
||||
var normalizedEntryName = NormalizeConfigPluginZipEntryName(entry.FullName);
|
||||
var destinationPath = GetConfigPluginZipEntryDestinationPath(destinationDirectoryFullPath, normalizedEntryName);
|
||||
|
||||
if (normalizedEntryName.EndsWith('/'))
|
||||
{
|
||||
if (entry.Length != 0)
|
||||
throw new InvalidDataException($"The enterprise configuration plugin archive contains a directory entry with data: '{entry.FullName}'.");
|
||||
|
||||
Directory.CreateDirectory(destinationPath);
|
||||
continue;
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)!);
|
||||
entry.ExtractToFile(destinationPath);
|
||||
}
|
||||
|
||||
if (!Directory.EnumerateFiles(destinationDirectory, "plugin.lua", SearchOption.AllDirectories).Any())
|
||||
throw new InvalidDataException("The enterprise configuration plugin archive does not contain a plugin.lua file.");
|
||||
}
|
||||
|
||||
private static string NormalizeConfigPluginZipEntryName(string entryName)
|
||||
{
|
||||
var normalizedEntryName = entryName.Replace('\\', '/');
|
||||
if (string.IsNullOrWhiteSpace(normalizedEntryName))
|
||||
throw new InvalidDataException("The enterprise configuration plugin archive contains an empty entry name.");
|
||||
|
||||
if (normalizedEntryName.Contains('\0'))
|
||||
throw new InvalidDataException($"The enterprise configuration plugin archive contains an invalid entry name: '{entryName}'.");
|
||||
|
||||
if (normalizedEntryName.StartsWith('/'))
|
||||
throw new InvalidDataException($"The enterprise configuration plugin archive contains a rooted entry name: '{entryName}'.");
|
||||
|
||||
if (normalizedEntryName is [_, ':', ..])
|
||||
throw new InvalidDataException($"The enterprise configuration plugin archive contains a drive-qualified entry name: '{entryName}'.");
|
||||
|
||||
var pathSegments = normalizedEntryName.Split('/', StringSplitOptions.RemoveEmptyEntries);
|
||||
if (pathSegments.Length == 0 || pathSegments.Any(segment => segment is "." or ".."))
|
||||
throw new InvalidDataException($"The enterprise configuration plugin archive contains an unsafe entry name: '{entryName}'.");
|
||||
|
||||
return normalizedEntryName;
|
||||
}
|
||||
|
||||
private static string GetConfigPluginZipEntryDestinationPath(string destinationDirectoryFullPath, string normalizedEntryName)
|
||||
{
|
||||
var pathSegments = normalizedEntryName.Split('/', StringSplitOptions.RemoveEmptyEntries);
|
||||
var relativePath = Path.Combine(pathSegments);
|
||||
var destinationPath = Path.GetFullPath(Path.Combine(destinationDirectoryFullPath, relativePath));
|
||||
if (!destinationPath.StartsWith(destinationDirectoryFullPath, StringComparison.Ordinal))
|
||||
throw new InvalidDataException($"The enterprise configuration plugin archive contains an entry outside the destination directory: '{normalizedEntryName}'.");
|
||||
|
||||
return destinationPath;
|
||||
}
|
||||
}
|
||||
@ -1,33 +1,73 @@
|
||||
(() => {
|
||||
const maximumRetryCount = 3;
|
||||
const retryIntervalMilliseconds = 500;
|
||||
const maximumRetryCount = 12;
|
||||
const reconnectModal = document.getElementById('reconnect-modal');
|
||||
const retryDelaysMilliseconds = [
|
||||
0,
|
||||
1_000,
|
||||
2_000,
|
||||
5_000,
|
||||
10_000,
|
||||
15_000,
|
||||
30_000,
|
||||
];
|
||||
|
||||
let currentReconnectionProcess = null;
|
||||
let isConnectionDown = false;
|
||||
|
||||
const delay = milliseconds => new Promise(resolve => setTimeout(resolve, milliseconds));
|
||||
|
||||
const getRetryDelayMilliseconds = attempt => retryDelaysMilliseconds[Math.min(attempt, retryDelaysMilliseconds.length - 1)];
|
||||
|
||||
const showReconnectModal = () => {
|
||||
if (reconnectModal)
|
||||
reconnectModal.style.display = 'flex';
|
||||
};
|
||||
|
||||
const hideReconnectModal = () => {
|
||||
if (reconnectModal)
|
||||
reconnectModal.style.display = 'none';
|
||||
};
|
||||
|
||||
const setReconnectModalText = text => {
|
||||
if (reconnectModal)
|
||||
reconnectModal.textContent = text;
|
||||
};
|
||||
|
||||
const startReconnectionProcess = () => {
|
||||
reconnectModal.style.display = 'block';
|
||||
showReconnectModal();
|
||||
|
||||
let isCanceled = false;
|
||||
let forceAttempt = false;
|
||||
|
||||
(async () => {
|
||||
for (let i = 0; i < maximumRetryCount; i++) {
|
||||
reconnectModal.innerText = `Attempting to reconnect: ${i + 1} of ${maximumRetryCount}`;
|
||||
const waitForNextAttempt = async milliseconds => {
|
||||
const startedAt = Date.now();
|
||||
while (!isCanceled && !forceAttempt && Date.now() - startedAt < milliseconds)
|
||||
await delay(250);
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, retryIntervalMilliseconds));
|
||||
forceAttempt = false;
|
||||
};
|
||||
|
||||
if (isCanceled) {
|
||||
void (async () => {
|
||||
for (let attempt = 0; attempt < maximumRetryCount && !isCanceled; attempt++) {
|
||||
setReconnectModalText(`Reconnecting to AI Studio (${attempt + 1}/${maximumRetryCount})...`);
|
||||
|
||||
const retryDelayMilliseconds = getRetryDelayMilliseconds(attempt);
|
||||
if (retryDelayMilliseconds > 0)
|
||||
await waitForNextAttempt(retryDelayMilliseconds);
|
||||
|
||||
if (isCanceled)
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await Blazor.reconnect();
|
||||
if (!result) {
|
||||
if (result === false) {
|
||||
// The server was reached, but the connection was rejected; reload the page.
|
||||
location.reload();
|
||||
return;
|
||||
}
|
||||
|
||||
// Successfully reconnected to the server.
|
||||
return;
|
||||
if (result === true)
|
||||
return;
|
||||
} catch {
|
||||
// Didn't reach the server; try again.
|
||||
}
|
||||
@ -40,25 +80,42 @@
|
||||
return {
|
||||
cancel: () => {
|
||||
isCanceled = true;
|
||||
reconnectModal.style.display = 'none';
|
||||
hideReconnectModal();
|
||||
},
|
||||
triggerImmediateAttempt: () => {
|
||||
forceAttempt = true;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
let currentReconnectionProcess = null;
|
||||
const triggerReconnectAfterWake = () => {
|
||||
if (isConnectionDown)
|
||||
currentReconnectionProcess?.triggerImmediateAttempt();
|
||||
};
|
||||
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (document.visibilityState === 'visible')
|
||||
triggerReconnectAfterWake();
|
||||
});
|
||||
|
||||
globalThis.addEventListener('pageshow', triggerReconnectAfterWake);
|
||||
|
||||
Blazor.start({
|
||||
circuit: {
|
||||
reconnectionHandler: {
|
||||
onConnectionDown: () => currentReconnectionProcess ??= startReconnectionProcess(),
|
||||
onConnectionDown: () => {
|
||||
isConnectionDown = true;
|
||||
currentReconnectionProcess ??= startReconnectionProcess();
|
||||
},
|
||||
onConnectionUp: () => {
|
||||
isConnectionDown = false;
|
||||
currentReconnectionProcess?.cancel();
|
||||
currentReconnectionProcess = null;
|
||||
}
|
||||
},
|
||||
|
||||
configureSignalR: function (builder) {
|
||||
builder.withServerTimeout(1_200_000);
|
||||
builder.withServerTimeout(120_000);
|
||||
builder.withKeepAliveInterval(30_000);
|
||||
},
|
||||
}
|
||||
|
||||
@ -1 +1,7 @@
|
||||
# v26.7.3, build 245 (2026-07-xx xx:xx UTC)
|
||||
- Improved the "My Tasks Assistant": you can now provide one or more documents in addition to text or use documents alone when asking to identify tasks.
|
||||
- Fixed an issue that could leave AI Studio unresponsive after waking the computer from sleep.
|
||||
- Fixed enterprise configuration plugins from Windows-created ZIP files not loading correctly on Linux when the ZIP contained plugin files inside a folder.
|
||||
- Upgraded Rust to v1.97.0.
|
||||
- Upgraded Tauri to v2.11.5.
|
||||
- Upgraded common dependencies.
|
||||
@ -0,0 +1,28 @@
|
||||
# Enterprise Configuration ZIP Backslashes
|
||||
|
||||
- Status: Active
|
||||
- Introduced: 2026-07-09
|
||||
- Remove after: when Microsoft fixes dotnet/runtime#27620 and dotnet/runtime#41914
|
||||
- Code references:
|
||||
- `app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Download.cs`
|
||||
|
||||
## User Impact
|
||||
|
||||
Some enterprise administrators create configuration plugin ZIP files on Windows. Depending on the packaging tool, entries inside the ZIP may use Windows-style backslashes, for example `O\plugin.lua`.
|
||||
|
||||
Without this shim, Unix systems extract those entries as files whose names contain literal backslash characters. The plugin loader then cannot find `plugin.lua`, so the enterprise configuration plugin is not activated.
|
||||
|
||||
## Compatibility Behavior
|
||||
|
||||
AI Studio manually extracts downloaded enterprise configuration plugin ZIP files. During extraction, entry names are normalized so both `/` and `\` are treated as archive path separators.
|
||||
|
||||
The extraction still preserves the archive structure and validates each entry before writing it to disk. Rooted paths, drive-qualified paths, and parent-directory traversal paths are rejected.
|
||||
|
||||
This works around the behavior described in dotnet/runtime#27620. A related upstream context for ZIP entry creation is dotnet/runtime#41914, where the ZIP specification requirement for forward slashes is discussed.
|
||||
|
||||
## Removal Checklist
|
||||
|
||||
- Confirm supported .NET runtimes and administrator packaging guidance no longer require accepting backslashes in enterprise ZIP entry names.
|
||||
- Replace the manual enterprise configuration plugin ZIP extraction with `ZipFile.ExtractToDirectory(...)`.
|
||||
- Remove `ExtractConfigPluginArchive(...)`, `NormalizeConfigPluginZipEntryName(...)`, and `GetConfigPluginZipEntryDestinationPath(...)`.
|
||||
- Update this document's status to `Removed`.
|
||||
@ -3,9 +3,9 @@
|
||||
244
|
||||
9.0.118 (commit c8cbca4ed1)
|
||||
9.0.17 (commit f2c8152eed)
|
||||
1.96.1 (commit 31fca3adb)
|
||||
1.97.0 (commit 2d8144b78)
|
||||
8.15.0
|
||||
2.11.2
|
||||
2.11.5
|
||||
4a15ff26655, release
|
||||
osx-arm64
|
||||
148.0.7763.0
|
||||
|
||||
117
runtime/Cargo.lock
generated
117
runtime/Cargo.lock
generated
@ -459,11 +459,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "atoi_simd"
|
||||
version = "0.17.0"
|
||||
version = "0.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ad17c7c205c2c28b527b9845eeb91cf1b4d008b438f98ce0e628227a822758e"
|
||||
checksum = "f3cdb3708a128e559a30fb830e8a77a5022ee6902806925c216658652b452a44"
|
||||
dependencies = [
|
||||
"debug_unsafe",
|
||||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -842,9 +843,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.16.0"
|
||||
version = "3.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
|
||||
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
@ -880,9 +881,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.11.1"
|
||||
version = "1.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
|
||||
checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@ -933,9 +934,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "calamine"
|
||||
version = "0.35.0"
|
||||
version = "0.36.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8822fe6253ca47aa5ad9a3be09f6fe7cd20c6a74e41b0aa42e8f4e3d523508df"
|
||||
checksum = "6975084f43060e56343ffba7f9731fa52a7dcf2e1cd8e2459fd4c6bf4a1bff59"
|
||||
dependencies = [
|
||||
"atoi_simd",
|
||||
"byteorder",
|
||||
@ -943,9 +944,9 @@ dependencies = [
|
||||
"encoding_rs",
|
||||
"fast-float2",
|
||||
"log",
|
||||
"quick-xml 0.39.2",
|
||||
"quick-xml 0.41.0",
|
||||
"serde",
|
||||
"zip 7.4.0",
|
||||
"zip 8.6.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1165,9 +1166,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cmov"
|
||||
version = "0.5.3"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f88a43d011fc4a6876cb7344703e297c71dda42494fee094d5f7c76bf13f746"
|
||||
checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a"
|
||||
|
||||
[[package]]
|
||||
name = "codepage"
|
||||
@ -1227,7 +1228,7 @@ dependencies = [
|
||||
"ph",
|
||||
"procfs",
|
||||
"quick_cache",
|
||||
"rand 0.10.1",
|
||||
"rand 0.10.2",
|
||||
"roaring",
|
||||
"schemars",
|
||||
"self_cell",
|
||||
@ -2517,7 +2518,7 @@ dependencies = [
|
||||
"i_overlay",
|
||||
"log",
|
||||
"num-traits",
|
||||
"rand 0.10.1",
|
||||
"rand 0.10.2",
|
||||
"rand_pcg",
|
||||
"robust",
|
||||
"rstar",
|
||||
@ -2765,7 +2766,7 @@ dependencies = [
|
||||
"log",
|
||||
"lz4_flex",
|
||||
"parking_lot",
|
||||
"rand 0.10.1",
|
||||
"rand 0.10.2",
|
||||
"serde",
|
||||
"serde_cbor",
|
||||
"serde_json",
|
||||
@ -3874,17 +3875,11 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lockfree-object-pool"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.32"
|
||||
version = "0.4.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a"
|
||||
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
|
||||
|
||||
[[package]]
|
||||
name = "lru-slab"
|
||||
@ -4025,7 +4020,7 @@ dependencies = [
|
||||
"pdfium-render",
|
||||
"pptx-to-md",
|
||||
"qdrant-edge",
|
||||
"rand 0.10.1",
|
||||
"rand 0.10.2",
|
||||
"rand_chacha 0.10.0",
|
||||
"rcgen",
|
||||
"rustls",
|
||||
@ -4034,7 +4029,7 @@ dependencies = [
|
||||
"sha2 0.11.0",
|
||||
"strum_macros",
|
||||
"sys-locale",
|
||||
"sysinfo 0.39.3",
|
||||
"sysinfo 0.39.6",
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"tauri-plugin-dialog",
|
||||
@ -5260,7 +5255,7 @@ dependencies = [
|
||||
"log",
|
||||
"ordered-float 5.3.0",
|
||||
"parking_lot",
|
||||
"rand 0.10.1",
|
||||
"rand 0.10.2",
|
||||
"segment",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@ -5303,7 +5298,7 @@ dependencies = [
|
||||
"ordered-float 5.3.0",
|
||||
"parking_lot",
|
||||
"permutation_iterator",
|
||||
"rand 0.10.1",
|
||||
"rand 0.10.2",
|
||||
"rayon",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@ -5321,9 +5316,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.39.2"
|
||||
version = "0.41.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "958f21e8e7ceb5a1aa7fa87fab28e7c75976e0bfe7e23ff069e0a260f894067d"
|
||||
checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1"
|
||||
dependencies = [
|
||||
"encoding_rs",
|
||||
"memchr",
|
||||
@ -5442,9 +5437,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.10.1"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207"
|
||||
checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80"
|
||||
dependencies = [
|
||||
"chacha20",
|
||||
"getrandom 0.4.2",
|
||||
@ -5513,7 +5508,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d431c2703ccf129de4d45253c03f49ebb22b97d6ad79ee3ecfc7e3f4862c1d8"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"rand 0.10.1",
|
||||
"rand 0.10.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -6038,7 +6033,7 @@ dependencies = [
|
||||
"procfs",
|
||||
"qdrant-rust-stemmers",
|
||||
"quantization",
|
||||
"rand 0.10.1",
|
||||
"rand 0.10.2",
|
||||
"rayon",
|
||||
"roaring",
|
||||
"schemars",
|
||||
@ -6355,7 +6350,7 @@ dependencies = [
|
||||
"log",
|
||||
"ordered-float 5.3.0",
|
||||
"parking_lot",
|
||||
"rand 0.10.1",
|
||||
"rand 0.10.2",
|
||||
"rmp-serde",
|
||||
"schemars",
|
||||
"segment",
|
||||
@ -6520,7 +6515,7 @@ dependencies = [
|
||||
"memmap2",
|
||||
"ordered-float 5.3.0",
|
||||
"parking_lot",
|
||||
"rand 0.10.1",
|
||||
"rand 0.10.2",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@ -6677,9 +6672,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sysinfo"
|
||||
version = "0.39.3"
|
||||
version = "0.39.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21d0d938c10fcda3e897e28aaddf4ab462375d411f4378cd63b1c945f69aba96"
|
||||
checksum = "d2071df9448915b71c4fe6d25deaf1c22f12bd234f01540b77312bb8e41361e6"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"memchr",
|
||||
@ -6779,9 +6774,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
|
||||
|
||||
[[package]]
|
||||
name = "tauri"
|
||||
version = "2.11.2"
|
||||
version = "2.11.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "437404997acf375d85f1177afa7e11bb971f274ed6a7b83a2a3e339015f4cc28"
|
||||
checksum = "667b20e2726d572dea2de7370da16e188eb06008faf9a92fab7cdc46791190b5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@ -6830,9 +6825,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-build"
|
||||
version = "2.6.2"
|
||||
version = "2.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4aa1f9055fc23919a54e4e125052bed16ed04aef0487086e758fe01a67b451c7"
|
||||
checksum = "bc9ce40b16101cb6ea63d3e221567affd1c3a9205f95d7bc574941a10636b632"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cargo_toml",
|
||||
@ -6851,9 +6846,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-codegen"
|
||||
version = "2.6.2"
|
||||
version = "2.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4a0319528a025a38c4078e7dae2c446f4e63620ddb0659a643ede1cb38f90e9"
|
||||
checksum = "08279169ff42f8fc45a1dbc9dcae888893ba95288142e5880c59b93a26d2cfc5"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"brotli",
|
||||
@ -6878,9 +6873,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-macros"
|
||||
version = "2.6.2"
|
||||
version = "2.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae6cb4e3896c21d2f6da5b31251d2faea0153bba56ed0e970f918115dbee4924"
|
||||
checksum = "e8b394794f399a421811d06966343e7933fcae92d59f5180b9388d1174497a45"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
@ -7071,9 +7066,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-runtime"
|
||||
version = "2.11.2"
|
||||
version = "2.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48222d7116c8807eaa6fe2f372e023fae125084e61e6eca6d70b7961cdf129ef"
|
||||
checksum = "b0b4bc95aed361b0019067d189a1174a603d460d0f6c72606512d59fc9c12ec8"
|
||||
dependencies = [
|
||||
"cookie",
|
||||
"dpi",
|
||||
@ -7096,9 +7091,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-runtime-wry"
|
||||
version = "2.11.2"
|
||||
version = "2.11.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b83849ee63ecb27a8e8d0fe51915ca215076914aca43f96db1179f0f415f6cd9"
|
||||
checksum = "4e6fac707727b7a2f48e4ded90976324267371073edbb415ffb73bb0458d203f"
|
||||
dependencies = [
|
||||
"gtk",
|
||||
"http",
|
||||
@ -7122,9 +7117,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-utils"
|
||||
version = "2.9.2"
|
||||
version = "2.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "092379df9a707631978e6c56b1bc2401d387f01e2d4a3c123360d167bbb9aa95"
|
||||
checksum = "3e176a18e67764923c4f1ce66f25ae4abe5f688384d5eb1a0fa6c77f3d90f887"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"brotli",
|
||||
@ -7609,9 +7604,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tray-icon"
|
||||
version = "0.23.1"
|
||||
version = "0.24.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15edbb0d80583e85ee8df283410038e17314df5cba30da2087a54a85216c0773"
|
||||
checksum = "65ba1e5f6b9ef9fd87e21b9c6f351554dbd717960089168fcfdef854686961dc"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"dirs",
|
||||
@ -7933,7 +7928,7 @@ dependencies = [
|
||||
"fs4",
|
||||
"log",
|
||||
"memmap2",
|
||||
"rand 0.10.1",
|
||||
"rand 0.10.2",
|
||||
"rand_distr",
|
||||
"rustix 1.1.4",
|
||||
"serde",
|
||||
@ -8434,9 +8429,9 @@ checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-native-keyring-store"
|
||||
version = "1.0.0"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5fd986f648459dd29aa252ed3a5ad11a60c0b1251bf81625fb03a86c69d274e"
|
||||
checksum = "063426e76fdec7438d56bb777f67e318a84a25c707b07e575cb8b78e10c028f8"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"keyring-core",
|
||||
@ -9340,9 +9335,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "7.4.0"
|
||||
version = "8.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc12baa6db2b15a140161ce53d72209dacea594230798c24774139b54ecaa980"
|
||||
checksum = "2d04a6b5381502aa6087c94c669499eb1602eb9c5e8198e534de571f7154809b"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"flate2",
|
||||
@ -9366,15 +9361,13 @@ checksum = "dfcd145825aace48cff44a8844de64bf75feec3080e0aa5cdbde72961ae51a65"
|
||||
|
||||
[[package]]
|
||||
name = "zopfli"
|
||||
version = "0.8.1"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946"
|
||||
checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"crc32fast",
|
||||
"lockfree-object-pool",
|
||||
"log",
|
||||
"once_cell",
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
|
||||
@ -6,10 +6,10 @@ description = "MindWork AI Studio"
|
||||
authors = ["Thorsten Sommer"]
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2.6.2", features = [] }
|
||||
tauri-build = { version = "2.6.3", features = [] }
|
||||
|
||||
[dependencies]
|
||||
tauri = { version = "2.11.2", features = [] }
|
||||
tauri = { version = "2.11.5", features = [] }
|
||||
tauri-plugin-window-state = { version = "2.4.1" }
|
||||
tauri-plugin-shell = "2.3.5"
|
||||
tauri-plugin-dialog = "2.7.1"
|
||||
@ -24,12 +24,12 @@ tokio-stream = "0.1.18"
|
||||
futures = "0.3.32"
|
||||
async-stream = "0.3.6"
|
||||
flexi_logger = "0.31.9"
|
||||
log = { version = "0.4.30", features = ["kv"] }
|
||||
log = { version = "0.4.33", features = ["kv"] }
|
||||
once_cell = "1.21.4"
|
||||
axum = { version = "0.8.9", features = ["http2", "json", "query", "tokio"] }
|
||||
axum-server = { version = "0.8.0", features = ["tls-rustls"] }
|
||||
rustls = { version = "0.23.28", default-features = false, features = ["aws_lc_rs"] }
|
||||
rand = "0.10.1"
|
||||
rand = "0.10.2"
|
||||
rand_chacha = "0.10.0"
|
||||
base64 = "0.22.1"
|
||||
aes = "0.9.1"
|
||||
@ -39,7 +39,7 @@ hmac = "0.13.0"
|
||||
sha2 = "0.11.0"
|
||||
rcgen = { version = "0.14.8", features = ["pem"] }
|
||||
file-format = "0.29.0"
|
||||
calamine = "0.35.0"
|
||||
calamine = "0.36.0"
|
||||
pdfium-render = "0.9.1"
|
||||
sys-locale = "0.3.2"
|
||||
whoami = "2.1.2"
|
||||
@ -47,8 +47,8 @@ cfg-if = "1.0.4"
|
||||
pptx-to-md = "0.4.0"
|
||||
tempfile = "3.27.0"
|
||||
strum_macros = "0.28.0"
|
||||
sysinfo = "0.39.3"
|
||||
bytes = "1.11.1"
|
||||
sysinfo = "0.39.6"
|
||||
bytes = "1.12.1"
|
||||
qdrant-edge = "0.7.2"
|
||||
|
||||
[patch.crates-io]
|
||||
@ -62,7 +62,7 @@ permutation_iterator = { git = "https://github.com/SommerEngineering/permutation
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
windows-registry = "0.6.1"
|
||||
windows-native-keyring-store = "1.0.0"
|
||||
windows-native-keyring-store = "1.1.0"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
apple-native-keyring-store = { version = "1.0.0", features = ["keychain"] }
|
||||
@ -72,7 +72,7 @@ dbus-secret-service-keyring-store = { version = "1.0.0", features = ["crypto-rus
|
||||
|
||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||
tauri-plugin-global-shortcut = "2"
|
||||
tauri-plugin-updater = "2.10.0"
|
||||
tauri-plugin-updater = "2.10.1"
|
||||
|
||||
[features]
|
||||
custom-protocol = ["tauri/custom-protocol"]
|
||||
|
||||
@ -223,7 +223,7 @@ fn file_logger_format(
|
||||
write_kv_pairs(w, record)?;
|
||||
|
||||
// Write the log message:
|
||||
write!(w, "{}", &record.args())
|
||||
write!(w, "{}", record.args())
|
||||
}
|
||||
|
||||
pub async fn get_log_paths(_token: APIToken) -> Json<LogPathsResponse> {
|
||||
|
||||
@ -1,31 +1,23 @@
|
||||
use std::error::Error;
|
||||
use std::sync::Mutex;
|
||||
use once_cell::sync::Lazy;
|
||||
use once_cell::sync::{Lazy, OnceCell};
|
||||
use pdfium_render::prelude::Pdfium;
|
||||
use log::{error, info, warn};
|
||||
|
||||
pub static PDFIUM_LIB_PATH: Lazy<Mutex<Option<String>>> = Lazy::new(|| Mutex::new(None));
|
||||
static PDFIUM: Lazy<Mutex<Option<Pdfium>>> = Lazy::new(|| Mutex::new(None));
|
||||
static PDFIUM: OnceCell<Pdfium> = OnceCell::new();
|
||||
|
||||
pub trait PdfiumInit {
|
||||
fn ai_studio_init() -> Result<Pdfium, Box<dyn Error + Send + Sync>>;
|
||||
fn ai_studio_init() -> Result<&'static Pdfium, Box<dyn Error + Send + Sync>>;
|
||||
}
|
||||
|
||||
impl PdfiumInit for Pdfium {
|
||||
|
||||
/// Initializes the PDFium library for AI Studio.
|
||||
fn ai_studio_init() -> Result<Pdfium, Box<dyn Error + Send + Sync>> {
|
||||
let mut pdfium = PDFIUM.lock().unwrap();
|
||||
if let Some(pdfium) = pdfium.as_ref() {
|
||||
return Ok(pdfium.clone());
|
||||
}
|
||||
|
||||
let loaded_pdfium = load_pdfium().map_err(|error| {
|
||||
fn ai_studio_init() -> Result<&'static Pdfium, Box<dyn Error + Send + Sync>> {
|
||||
PDFIUM.get_or_try_init(|| load_pdfium().map_err(|error| {
|
||||
Box::new(std::io::Error::other(error)) as Box<dyn Error + Send + Sync>
|
||||
})?;
|
||||
*pdfium = Some(loaded_pdfium.clone());
|
||||
|
||||
Ok(loaded_pdfium)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,4 +68,4 @@ fn load_pdfium() -> Result<Pdfium, String> {
|
||||
Err(error_message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user