diff --git a/code/ai/PP-StructureV3.yaml b/code/ai/PP-StructureV3.yaml new file mode 100644 index 00000000..62278e98 --- /dev/null +++ b/code/ai/PP-StructureV3.yaml @@ -0,0 +1,235 @@ + +pipeline_name: PP-StructureV3 + +batch_size: 8 + +use_doc_preprocessor: False +use_seal_recognition: False +use_table_recognition: True +use_formula_recognition: True +use_chart_recognition: False +use_region_detection: True +format_block_content: False +markdown_ignore_labels: + - number + - footnote + - header + - header_image + - footer + - footer_image + - aside_text + +SubModules: + LayoutDetection: + module_name: layout_detection + model_name: PP-DocLayout_plus-L + model_dir: null + batch_size: 8 + threshold: + 0: 0.3 # paragraph_title + 1: 0.5 # image + 2: 0.4 # text + 3: 0.5 # number + 4: 0.5 # abstract + 5: 0.5 # content + 6: 0.5 # figure_table_chart_title + 7: 0.3 # formula + 8: 0.5 # table + 9: 0.5 # reference + 10: 0.5 # doc_title + 11: 0.5 # footnote + 12: 0.5 # header + 13: 0.5 # algorithm + 14: 0.5 # footer + 15: 0.45 # seal + 16: 0.5 # chart + 17: 0.5 # formula_number + 18: 0.5 # aside_text + 19: 0.5 # reference_content + layout_nms: True + layout_unclip_ratio: [1.0, 1.0] + layout_merge_bboxes_mode: + 0: "large" # paragraph_title + 1: "large" # image + 2: "union" # text + 3: "union" # number + 4: "union" # abstract + 5: "union" # content + 6: "union" # figure_table_chart_title + 7: "large" # formula + 8: "union" # table + 9: "union" # reference + 10: "union" # doc_title + 11: "union" # footnote + 12: "union" # header + 13: "union" # algorithm + 14: "union" # footer + 15: "union" # seal + 16: "large" # chart + 17: "union" # formula_number + 18: "union" # aside_text + 19: "union" # reference_content + ChartRecognition: + module_name: chart_recognition + model_name: PP-Chart2Table + model_dir: null + batch_size: 1 + RegionDetection: + module_name: layout_detection + model_name: PP-DocBlockLayout + model_dir: null + layout_nms: True + layout_merge_bboxes_mode: "small" + +SubPipelines: + DocPreprocessor: + pipeline_name: doc_preprocessor + batch_size: 8 + use_doc_orientation_classify: True + use_doc_unwarping: True + SubModules: + DocOrientationClassify: + module_name: doc_text_orientation + model_name: PP-LCNet_x1_0_doc_ori + model_dir: null + batch_size: 8 + DocUnwarping: + module_name: image_unwarping + model_name: UVDoc + model_dir: null + + GeneralOCR: + pipeline_name: OCR + batch_size: 8 + text_type: general + use_doc_preprocessor: False + use_textline_orientation: True + SubModules: + TextDetection: + module_name: text_detection + model_name: PP-OCRv5_server_det + model_dir: null + limit_side_len: 736 + limit_type: min + max_side_limit: 4000 + thresh: 0.3 + box_thresh: 0.6 + unclip_ratio: 1.5 + TextLineOrientation: + module_name: textline_orientation + model_name: PP-LCNet_x1_0_textline_ori + model_dir: null + batch_size: 8 + TextRecognition: + module_name: text_recognition + model_name: PP-OCRv5_server_rec + model_dir: null + batch_size: 8 + score_thresh: 0.0 + + + TableRecognition: + pipeline_name: table_recognition_v2 + use_layout_detection: False + use_doc_preprocessor: False + use_ocr_model: False + SubModules: + TableClassification: + module_name: table_classification + model_name: PP-LCNet_x1_0_table_cls + model_dir: null + + WiredTableStructureRecognition: + module_name: table_structure_recognition + model_name: SLANeXt_wired + model_dir: null + + WirelessTableStructureRecognition: + module_name: table_structure_recognition + model_name: SLANet_plus + model_dir: null + + WiredTableCellsDetection: + module_name: table_cells_detection + model_name: RT-DETR-L_wired_table_cell_det + model_dir: null + + WirelessTableCellsDetection: + module_name: table_cells_detection + model_name: RT-DETR-L_wireless_table_cell_det + model_dir: null + + TableOrientationClassify: + module_name: doc_text_orientation + model_name: PP-LCNet_x1_0_doc_ori + model_dir: null + SubPipelines: + GeneralOCR: + pipeline_name: OCR + text_type: general + use_doc_preprocessor: False + use_textline_orientation: True + SubModules: + TextDetection: + module_name: text_detection + model_name: PP-OCRv5_server_det + model_dir: null + limit_side_len: 736 + limit_type: min + max_side_limit: 4000 + thresh: 0.3 + box_thresh: 0.4 + unclip_ratio: 1.5 + TextLineOrientation: + module_name: textline_orientation + model_name: PP-LCNet_x1_0_textline_ori + model_dir: null + batch_size: 8 + TextRecognition: + module_name: text_recognition + model_name: PP-OCRv5_server_rec + model_dir: null + batch_size: 8 + score_thresh: 0.0 + + SealRecognition: + pipeline_name: seal_recognition + batch_size: 8 + use_layout_detection: False + use_doc_preprocessor: False + SubPipelines: + SealOCR: + pipeline_name: OCR + batch_size: 8 + text_type: seal + use_doc_preprocessor: False + use_textline_orientation: False + SubModules: + TextDetection: + module_name: seal_text_detection + model_name: PP-OCRv4_server_seal_det + model_dir: null + limit_side_len: 736 + limit_type: min + max_side_limit: 4000 + thresh: 0.2 + box_thresh: 0.6 + unclip_ratio: 0.5 + TextRecognition: + module_name: text_recognition + model_name: PP-OCRv5_server_rec + model_dir: null + batch_size: 8 + score_thresh: 0 + + FormulaRecognition: + pipeline_name: formula_recognition + batch_size: 8 + use_layout_detection: False + use_doc_preprocessor: False + SubModules: + FormulaRecognition: + module_name: formula_recognition + model_name: PP-FormulaNet_plus-L + model_dir: null + batch_size: 8 diff --git a/code/ai/ai-springboot/.gitattributes b/code/ai/ai-springboot/.gitattributes new file mode 100644 index 00000000..3b41682a --- /dev/null +++ b/code/ai/ai-springboot/.gitattributes @@ -0,0 +1,2 @@ +/mvnw text eol=lf +*.cmd text eol=crlf diff --git a/code/ai/ai-springboot/.gitignore b/code/ai/ai-springboot/.gitignore new file mode 100644 index 00000000..667aaef0 --- /dev/null +++ b/code/ai/ai-springboot/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/code/ai/ai-springboot/.mvn/wrapper/maven-wrapper.properties b/code/ai/ai-springboot/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 00000000..216df058 --- /dev/null +++ b/code/ai/ai-springboot/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,3 @@ +wrapperVersion=3.3.4 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.16/apache-maven-3.9.16-bin.zip diff --git a/code/ai/ai-springboot/mvnw b/code/ai/ai-springboot/mvnw new file mode 100644 index 00000000..bd8896bf --- /dev/null +++ b/code/ai/ai-springboot/mvnw @@ -0,0 +1,295 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.4 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +scriptDir="$(dirname "$0")" +scriptName="$(basename "$0")" + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +actualDistributionDir="" + +# First try the expected directory name (for regular distributions) +if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then + if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then + actualDistributionDir="$distributionUrlNameMain" + fi +fi + +# If not found, search for any directory with the Maven executable (for snapshots) +if [ -z "$actualDistributionDir" ]; then + # enable globbing to iterate over items + set +f + for dir in "$TMP_DOWNLOAD_DIR"/*; do + if [ -d "$dir" ]; then + if [ -f "$dir/bin/$MVN_CMD" ]; then + actualDistributionDir="$(basename "$dir")" + break + fi + fi + done + set -f +fi + +if [ -z "$actualDistributionDir" ]; then + verbose "Contents of $TMP_DOWNLOAD_DIR:" + verbose "$(ls -la "$TMP_DOWNLOAD_DIR")" + die "Could not find Maven distribution directory in extracted archive" +fi + +verbose "Found extracted Maven distribution directory: $actualDistributionDir" +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/code/ai/ai-springboot/mvnw.cmd b/code/ai/ai-springboot/mvnw.cmd new file mode 100644 index 00000000..92450f93 --- /dev/null +++ b/code/ai/ai-springboot/mvnw.cmd @@ -0,0 +1,189 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.4 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' + +$MAVEN_M2_PATH = "$HOME/.m2" +if ($env:MAVEN_USER_HOME) { + $MAVEN_M2_PATH = "$env:MAVEN_USER_HOME" +} + +if (-not (Test-Path -Path $MAVEN_M2_PATH)) { + New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null +} + +$MAVEN_WRAPPER_DISTS = $null +if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) { + $MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists" +} else { + $MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists" +} + +$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain" +$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +$actualDistributionDir = "" + +# First try the expected directory name (for regular distributions) +$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain" +$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD" +if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) { + $actualDistributionDir = $distributionUrlNameMain +} + +# If not found, search for any directory with the Maven executable (for snapshots) +if (!$actualDistributionDir) { + Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object { + $testPath = Join-Path $_.FullName "bin/$MVN_CMD" + if (Test-Path -Path $testPath -PathType Leaf) { + $actualDistributionDir = $_.Name + } + } +} + +if (!$actualDistributionDir) { + Write-Error "Could not find Maven distribution directory in extracted archive" +} + +Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir" +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/code/ai/ai-springboot/pom.xml b/code/ai/ai-springboot/pom.xml new file mode 100644 index 00000000..2f3c793f --- /dev/null +++ b/code/ai/ai-springboot/pom.xml @@ -0,0 +1,129 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 4.0.6 + + + cn.g3soft + ai-springboot + 0.0.1-SNAPSHOT + ai-springboot + ai-springboot + + + + + + + + + + + + + + + 21 + 2.0.0-RC1 + + + + org.springframework.boot + spring-boot-starter-webmvc + + + org.springframework.ai + spring-ai-starter-model-ollama + + + org.springframework.ai + spring-ai-starter-model-openai + + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-webmvc-test + test + + + + + + org.springframework.ai + spring-ai-bom + ${spring-ai.version} + pom + import + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + default-compile + compile + + compile + + + + + org.projectlombok + lombok + + + + + + default-testCompile + test-compile + + testCompile + + + + + org.projectlombok + lombok + + + + + + + + + + diff --git a/code/ai/ai-springboot/src/main/java/cn/g3soft/AiSpringbootApplication.java b/code/ai/ai-springboot/src/main/java/cn/g3soft/AiSpringbootApplication.java new file mode 100644 index 00000000..d50191b3 --- /dev/null +++ b/code/ai/ai-springboot/src/main/java/cn/g3soft/AiSpringbootApplication.java @@ -0,0 +1,13 @@ +package cn.g3soft; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class AiSpringbootApplication { + + public static void main(String[] args) { + SpringApplication.run(AiSpringbootApplication.class, args); + } + +} diff --git a/code/ai/ai-springboot/src/main/java/cn/g3soft/config/FileStorageProperties.java b/code/ai/ai-springboot/src/main/java/cn/g3soft/config/FileStorageProperties.java new file mode 100644 index 00000000..e8b9d366 --- /dev/null +++ b/code/ai/ai-springboot/src/main/java/cn/g3soft/config/FileStorageProperties.java @@ -0,0 +1,82 @@ +package cn.g3soft.config; + +import java.nio.file.Path; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +@Component +@ConfigurationProperties(prefix = "app.file") +public class FileStorageProperties { + + private String uploadDir = "uploads"; + + private String publicBaseUrl = ""; + + private String accessUrlPrefix = "/uploads"; + + public String getUploadDir() { + return uploadDir; + } + + public void setUploadDir(String uploadDir) { + this.uploadDir = uploadDir; + } + + public String getAccessUrlPrefix() { + return accessUrlPrefix; + } + + public String getPublicBaseUrl() { + return publicBaseUrl; + } + + public void setPublicBaseUrl(String publicBaseUrl) { + this.publicBaseUrl = publicBaseUrl; + } + + public void setAccessUrlPrefix(String accessUrlPrefix) { + this.accessUrlPrefix = accessUrlPrefix; + } + + public Path getUploadPath() { + String dir = StringUtils.hasText(uploadDir) ? uploadDir : "uploads"; + return Path.of(dir).toAbsolutePath().normalize(); + } + + public String getNormalizedAccessUrlPrefix() { + String prefix = StringUtils.hasText(accessUrlPrefix) ? accessUrlPrefix.trim() : "/uploads"; + + if (!prefix.startsWith("/")) { + prefix = "/" + prefix; + } + + while (prefix.length() > 1 && prefix.endsWith("/")) { + prefix = prefix.substring(0, prefix.length() - 1); + } + + return prefix; + } + + public String getResourceHandlerPattern() { + return getNormalizedAccessUrlPrefix() + "/**"; + } + + public String buildAccessUrl(String fileName) { + return getNormalizedPublicBaseUrl() + getNormalizedAccessUrlPrefix() + "/" + fileName; + } + + private String getNormalizedPublicBaseUrl() { + if (!StringUtils.hasText(publicBaseUrl)) { + return ""; + } + + String baseUrl = publicBaseUrl.trim(); + + while (baseUrl.endsWith("/")) { + baseUrl = baseUrl.substring(0, baseUrl.length() - 1); + } + + return baseUrl; + } +} diff --git a/code/ai/ai-springboot/src/main/java/cn/g3soft/config/JacksonConfig.java b/code/ai/ai-springboot/src/main/java/cn/g3soft/config/JacksonConfig.java new file mode 100644 index 00000000..8c8386d2 --- /dev/null +++ b/code/ai/ai-springboot/src/main/java/cn/g3soft/config/JacksonConfig.java @@ -0,0 +1,14 @@ +package cn.g3soft.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class JacksonConfig { + + @Bean + public ObjectMapper objectMapper() { + return new ObjectMapper().findAndRegisterModules(); + } +} diff --git a/code/ai/ai-springboot/src/main/java/cn/g3soft/config/PpStructureProperties.java b/code/ai/ai-springboot/src/main/java/cn/g3soft/config/PpStructureProperties.java new file mode 100644 index 00000000..0d301fad --- /dev/null +++ b/code/ai/ai-springboot/src/main/java/cn/g3soft/config/PpStructureProperties.java @@ -0,0 +1,81 @@ +package cn.g3soft.config; + +import java.net.URI; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +@Component +@ConfigurationProperties(prefix = "app.pp-structure") +public class PpStructureProperties { + + private String baseUrl = "http://192.168.1.137:8866"; + + private String endpointPath = "/layout-parsing"; + + private int requestTimeoutSeconds = 120; + + private int connectTimeoutSeconds = 10; + + public String getBaseUrl() { + return baseUrl; + } + + public void setBaseUrl(String baseUrl) { + this.baseUrl = baseUrl; + } + + public String getEndpointPath() { + return endpointPath; + } + + public void setEndpointPath(String endpointPath) { + this.endpointPath = endpointPath; + } + + public int getRequestTimeoutSeconds() { + return requestTimeoutSeconds; + } + + public void setRequestTimeoutSeconds(int requestTimeoutSeconds) { + this.requestTimeoutSeconds = requestTimeoutSeconds; + } + + public int getConnectTimeoutSeconds() { + return connectTimeoutSeconds; + } + + public void setConnectTimeoutSeconds(int connectTimeoutSeconds) { + this.connectTimeoutSeconds = connectTimeoutSeconds; + } + + public URI getRecognitionUri() { + String normalizedBaseUrl = normalizeBaseUrl(baseUrl); + String normalizedPath = normalizeEndpointPath(endpointPath); + return URI.create(normalizedBaseUrl + normalizedPath); + } + + private String normalizeBaseUrl(String value) { + String normalized = StringUtils.hasText(value) ? value.trim() : "http://192.168.1.137:8866"; + + if (!normalized.startsWith("http://") && !normalized.startsWith("https://")) { + normalized = "http://" + normalized; + } + + while (normalized.endsWith("/")) { + normalized = normalized.substring(0, normalized.length() - 1); + } + + return normalized; + } + + private String normalizeEndpointPath(String value) { + String normalized = StringUtils.hasText(value) ? value.trim() : "/layout-parsing"; + + if (!normalized.startsWith("/")) { + normalized = "/" + normalized; + } + + return normalized; + } +} diff --git a/code/ai/ai-springboot/src/main/java/cn/g3soft/config/WebMvcConfig.java b/code/ai/ai-springboot/src/main/java/cn/g3soft/config/WebMvcConfig.java new file mode 100644 index 00000000..cc6ed7bd --- /dev/null +++ b/code/ai/ai-springboot/src/main/java/cn/g3soft/config/WebMvcConfig.java @@ -0,0 +1,39 @@ +package cn.g3soft.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebMvcConfig implements WebMvcConfigurer { + + private final FileStorageProperties fileStorageProperties; + + public WebMvcConfig(FileStorageProperties fileStorageProperties) { + this.fileStorageProperties = fileStorageProperties; + } + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + String resourceLocation = fileStorageProperties.getUploadPath().toUri().toString(); + + if (!resourceLocation.endsWith("/")) { + resourceLocation += "/"; + } + + registry + .addResourceHandler(fileStorageProperties.getResourceHandlerPattern()) + .addResourceLocations(resourceLocation); + } + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry + .addMapping("/api/**") + .allowedOriginPatterns("*") + .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") + .allowedHeaders("*") + .maxAge(3600); + } +} diff --git a/code/ai/ai-springboot/src/main/java/cn/g3soft/file/FileStorageService.java b/code/ai/ai-springboot/src/main/java/cn/g3soft/file/FileStorageService.java new file mode 100644 index 00000000..36cb70c4 --- /dev/null +++ b/code/ai/ai-springboot/src/main/java/cn/g3soft/file/FileStorageService.java @@ -0,0 +1,69 @@ +package cn.g3soft.file; + +import cn.g3soft.config.FileStorageProperties; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Locale; +import java.util.UUID; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.server.ResponseStatusException; + +@Service +public class FileStorageService { + + private final FileStorageProperties fileStorageProperties; + + public FileStorageService(FileStorageProperties fileStorageProperties) { + this.fileStorageProperties = fileStorageProperties; + } + + public FileUploadResponse store(MultipartFile file) { + if (file == null || file.isEmpty()) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "上传文件不能为空"); + } + + String originalName = StringUtils.cleanPath(file.getOriginalFilename() == null ? "" : file.getOriginalFilename()); + String storedFileName = UUID.randomUUID().toString().replace("-", "") + getExtension(originalName); + Path uploadPath = fileStorageProperties.getUploadPath(); + Path targetPath = uploadPath.resolve(storedFileName).normalize(); + + if (!targetPath.startsWith(uploadPath)) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "文件名不合法"); + } + + try { + Files.createDirectories(uploadPath); + file.transferTo(targetPath); + } catch (IOException ex) { + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "文件保存失败", ex); + } + + return new FileUploadResponse( + originalName, + storedFileName, + fileStorageProperties.buildAccessUrl(storedFileName), + file.getSize(), + file.getContentType() + ); + } + + private String getExtension(String originalName) { + if (!StringUtils.hasText(originalName)) { + return ""; + } + + int slashIndex = Math.max(originalName.lastIndexOf('/'), originalName.lastIndexOf('\\')); + String fileName = originalName.substring(slashIndex + 1); + int dotIndex = fileName.lastIndexOf('.'); + + if (dotIndex < 0 || dotIndex == fileName.length() - 1) { + return ""; + } + + return fileName.substring(dotIndex).toLowerCase(Locale.ROOT); + } +} diff --git a/code/ai/ai-springboot/src/main/java/cn/g3soft/file/FileUploadController.java b/code/ai/ai-springboot/src/main/java/cn/g3soft/file/FileUploadController.java new file mode 100644 index 00000000..bec23539 --- /dev/null +++ b/code/ai/ai-springboot/src/main/java/cn/g3soft/file/FileUploadController.java @@ -0,0 +1,23 @@ +package cn.g3soft.file; + +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +@RestController +@RequestMapping("/api/files") +public class FileUploadController { + + private final FileStorageService fileStorageService; + + public FileUploadController(FileStorageService fileStorageService) { + this.fileStorageService = fileStorageService; + } + + @PostMapping("/upload") + public FileUploadResponse upload(@RequestParam("file") MultipartFile file) { + return fileStorageService.store(file); + } +} diff --git a/code/ai/ai-springboot/src/main/java/cn/g3soft/file/FileUploadResponse.java b/code/ai/ai-springboot/src/main/java/cn/g3soft/file/FileUploadResponse.java new file mode 100644 index 00000000..112011e8 --- /dev/null +++ b/code/ai/ai-springboot/src/main/java/cn/g3soft/file/FileUploadResponse.java @@ -0,0 +1,10 @@ +package cn.g3soft.file; + +public record FileUploadResponse( + String originalName, + String fileName, + String url, + long size, + String contentType +) { +} diff --git a/code/ai/ai-springboot/src/main/java/cn/g3soft/recognition/RecognitionController.java b/code/ai/ai-springboot/src/main/java/cn/g3soft/recognition/RecognitionController.java new file mode 100644 index 00000000..8a8717f9 --- /dev/null +++ b/code/ai/ai-springboot/src/main/java/cn/g3soft/recognition/RecognitionController.java @@ -0,0 +1,22 @@ +package cn.g3soft.recognition; + +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/recognitions") +public class RecognitionController { + + private final RecognitionService recognitionService; + + public RecognitionController(RecognitionService recognitionService) { + this.recognitionService = recognitionService; + } + + @PostMapping("/start") + public RecognitionResponse start(@RequestBody RecognitionRequest request) { + return recognitionService.recognize(request); + } +} diff --git a/code/ai/ai-springboot/src/main/java/cn/g3soft/recognition/RecognitionRequest.java b/code/ai/ai-springboot/src/main/java/cn/g3soft/recognition/RecognitionRequest.java new file mode 100644 index 00000000..5cb831f8 --- /dev/null +++ b/code/ai/ai-springboot/src/main/java/cn/g3soft/recognition/RecognitionRequest.java @@ -0,0 +1,7 @@ +package cn.g3soft.recognition; + +public record RecognitionRequest( + String url, + String type +) { +} diff --git a/code/ai/ai-springboot/src/main/java/cn/g3soft/recognition/RecognitionResponse.java b/code/ai/ai-springboot/src/main/java/cn/g3soft/recognition/RecognitionResponse.java new file mode 100644 index 00000000..56bdbd2c --- /dev/null +++ b/code/ai/ai-springboot/src/main/java/cn/g3soft/recognition/RecognitionResponse.java @@ -0,0 +1,12 @@ +package cn.g3soft.recognition; + +import com.fasterxml.jackson.databind.JsonNode; + +public record RecognitionResponse( + String url, + String type, + String resultFileName, + String resultFileUrl, + JsonNode ppStructureResult +) { +} diff --git a/code/ai/ai-springboot/src/main/java/cn/g3soft/recognition/RecognitionService.java b/code/ai/ai-springboot/src/main/java/cn/g3soft/recognition/RecognitionService.java new file mode 100644 index 00000000..8c93e7a5 --- /dev/null +++ b/code/ai/ai-springboot/src/main/java/cn/g3soft/recognition/RecognitionService.java @@ -0,0 +1,146 @@ +package cn.g3soft.recognition; + +import cn.g3soft.config.PpStructureProperties; +import cn.g3soft.config.FileStorageProperties; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import java.net.http.HttpConnectTimeoutException; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.net.http.HttpTimeoutException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Duration; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.UUID; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; +import org.springframework.web.server.ResponseStatusException; + +@Service +public class RecognitionService { + + private static final Logger log = LoggerFactory.getLogger(RecognitionService.class); + + private final PpStructureProperties ppStructureProperties; + + private final FileStorageProperties fileStorageProperties; + + private final ObjectMapper objectMapper; + + public RecognitionService(PpStructureProperties ppStructureProperties, FileStorageProperties fileStorageProperties, ObjectMapper objectMapper) { + this.ppStructureProperties = ppStructureProperties; + this.fileStorageProperties = fileStorageProperties; + this.objectMapper = objectMapper; + } + + public RecognitionResponse recognize(RecognitionRequest recognitionRequest) { + validateRequest(recognitionRequest); + + JsonNode ppStructureResult = callPpStructure(recognitionRequest); + String resultText = ppStructureResult.toPrettyString(); + String resultFileName = saveRecognitionResult(resultText); + log.info("PP-StructureV3 recognition result, type={}, url={}, result={}", + recognitionRequest.type(), + recognitionRequest.url(), + resultText); + log.info("PP-StructureV3 recognition result txt saved, fileName={}, url={}", + resultFileName, + fileStorageProperties.buildAccessUrl(resultFileName)); + + return new RecognitionResponse( + recognitionRequest.url(), + recognitionRequest.type(), + resultFileName, + fileStorageProperties.buildAccessUrl(resultFileName), + ppStructureResult + ); + } + + private JsonNode callPpStructure(RecognitionRequest recognitionRequest) { + Map ppStructureRequest = new LinkedHashMap<>(); + ppStructureRequest.put("file", recognitionRequest.url()); + + try { + String requestBody = objectMapper.writeValueAsString(ppStructureRequest); + log.info("PP-StructureV3 request uri={}", ppStructureProperties.getRecognitionUri()); + log.info("PP-StructureV3 request body={}", requestBody); + + HttpRequest request = HttpRequest + .newBuilder(ppStructureProperties.getRecognitionUri()) + .timeout(Duration.ofSeconds(ppStructureProperties.getRequestTimeoutSeconds())) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(requestBody)) + .build(); + + HttpClient client = HttpClient + .newBuilder() + .connectTimeout(Duration.ofSeconds(ppStructureProperties.getConnectTimeoutSeconds())) + .build(); + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + String responseBody = response.body(); + + log.info("PP-StructureV3 response status={}", response.statusCode()); + log.info("PP-StructureV3 response headers={}", response.headers().map()); + log.info("PP-StructureV3 response body={}", responseBody); + + if (response.statusCode() < 200 || response.statusCode() >= 300) { + throw new ResponseStatusException( + HttpStatus.BAD_GATEWAY, + "PP-StructureV3识别失败,状态码:" + response.statusCode() + ",响应:" + responseBody + ); + } + + return objectMapper.readTree(responseBody); + } catch (HttpConnectTimeoutException ex) { + log.error("PP-StructureV3 connect timeout, uri={}", ppStructureProperties.getRecognitionUri(), ex); + throw new ResponseStatusException(HttpStatus.GATEWAY_TIMEOUT, "PP-StructureV3连接超时", ex); + } catch (HttpTimeoutException ex) { + log.error("PP-StructureV3 request timeout, uri={}", ppStructureProperties.getRecognitionUri(), ex); + throw new ResponseStatusException(HttpStatus.GATEWAY_TIMEOUT, "PP-StructureV3识别请求超时", ex); + } catch (IOException ex) { + log.error("PP-StructureV3 request or response parse failed, uri={}", ppStructureProperties.getRecognitionUri(), ex); + throw new ResponseStatusException(HttpStatus.BAD_GATEWAY, "PP-StructureV3识别响应解析失败", ex); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + log.error("PP-StructureV3 request interrupted, uri={}", ppStructureProperties.getRecognitionUri(), ex); + throw new ResponseStatusException(HttpStatus.BAD_GATEWAY, "PP-StructureV3识别请求被中断", ex); + } + } + + private void validateRequest(RecognitionRequest request) { + if (request == null || !StringUtils.hasText(request.url())) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "url不能为空"); + } + + if (!StringUtils.hasText(request.type())) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "type不能为空"); + } + } + + private String saveRecognitionResult(String resultText) { + String resultFileName = UUID.randomUUID().toString().replace("-", "") + ".txt"; + Path uploadPath = fileStorageProperties.getUploadPath(); + Path targetPath = uploadPath.resolve(resultFileName).normalize(); + + if (!targetPath.startsWith(uploadPath)) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "识别结果文件名不合法"); + } + + try { + Files.createDirectories(uploadPath); + Files.writeString(targetPath, resultText, StandardCharsets.UTF_8); + return resultFileName; + } catch (IOException ex) { + log.error("Save PP-StructureV3 recognition result txt failed, path={}", targetPath, ex); + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "识别结果文件保存失败", ex); + } + } +} diff --git a/code/ai/ai-springboot/src/main/resources/application.yml b/code/ai/ai-springboot/src/main/resources/application.yml new file mode 100644 index 00000000..ee9985d1 --- /dev/null +++ b/code/ai/ai-springboot/src/main/resources/application.yml @@ -0,0 +1,20 @@ +server: + port: 8082 + +app: + file: + upload-dir: D:/data/test + public-base-url: http://192.168.1.137:8082 + access-url-prefix: /uploads + pp-structure: + base-url: http://192.168.1.137:8866 + endpoint-path: /layout-parsing + request-timeout-seconds: 15 + connect-timeout-seconds: 15 + +spring: + ai: + openai: + api-key: xxx + base-url: https://api.openai.com + model: xxx diff --git a/code/ai/ai-springboot/src/test/java/cn/g3soft/ai/AiSpringbootApplicationTests.java b/code/ai/ai-springboot/src/test/java/cn/g3soft/ai/AiSpringbootApplicationTests.java new file mode 100644 index 00000000..8aa8bd13 --- /dev/null +++ b/code/ai/ai-springboot/src/test/java/cn/g3soft/ai/AiSpringbootApplicationTests.java @@ -0,0 +1,13 @@ +package cn.g3soft.ai; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class AiSpringbootApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/code/ai/ai-vue/.env.development b/code/ai/ai-vue/.env.development new file mode 100644 index 00000000..a3d0c3ff --- /dev/null +++ b/code/ai/ai-vue/.env.development @@ -0,0 +1 @@ +VITE_API_BASE_URL=http://192.168.1.137:8082 diff --git a/code/ai/ai-vue/.gitignore b/code/ai/ai-vue/.gitignore new file mode 100644 index 00000000..a547bf36 --- /dev/null +++ b/code/ai/ai-vue/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/code/ai/ai-vue/.oxfmtrc.json b/code/ai/ai-vue/.oxfmtrc.json new file mode 100644 index 00000000..55c15df3 --- /dev/null +++ b/code/ai/ai-vue/.oxfmtrc.json @@ -0,0 +1,4 @@ +{ + "$schema": "./node_modules/oxfmt/configuration_schema.json", + "ignorePatterns": [] +} diff --git a/code/ai/ai-vue/.vscode/extensions.json b/code/ai/ai-vue/.vscode/extensions.json new file mode 100644 index 00000000..a7cea0b0 --- /dev/null +++ b/code/ai/ai-vue/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["Vue.volar"] +} diff --git a/code/ai/ai-vue/README.md b/code/ai/ai-vue/README.md new file mode 100644 index 00000000..33895ab2 --- /dev/null +++ b/code/ai/ai-vue/README.md @@ -0,0 +1,5 @@ +# Vue 3 + TypeScript + Vite + +This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 ` + + diff --git a/code/ai/ai-vue/package.json b/code/ai/ai-vue/package.json new file mode 100644 index 00000000..778a3ca2 --- /dev/null +++ b/code/ai/ai-vue/package.json @@ -0,0 +1,27 @@ +{ + "name": "ai-vue", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vue-tsc -b && vite build", + "preview": "vite preview", + "fmt": "oxfmt", + "fmt:check": "oxfmt --check" + }, + "dependencies": { + "alova": "^3.5.1", + "naive-ui": "^2.44.1", + "vue": "^3.5.34" + }, + "devDependencies": { + "@types/node": "^24.12.3", + "@vitejs/plugin-vue": "^6.0.6", + "@vue/tsconfig": "^0.9.1", + "oxfmt": "^0.54.0", + "typescript": "~6.0.2", + "vite": "^8.0.12", + "vue-tsc": "^3.2.8" + } +} diff --git a/code/ai/ai-vue/pnpm-lock.yaml b/code/ai/ai-vue/pnpm-lock.yaml new file mode 100644 index 00000000..5cbe8be7 --- /dev/null +++ b/code/ai/ai-vue/pnpm-lock.yaml @@ -0,0 +1,1205 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + alova: + specifier: ^3.5.1 + version: 3.5.1 + naive-ui: + specifier: ^2.44.1 + version: 2.44.1(vue@3.5.35(typescript@6.0.3)) + vue: + specifier: ^3.5.34 + version: 3.5.35(typescript@6.0.3) + devDependencies: + '@types/node': + specifier: ^24.12.3 + version: 24.13.1 + '@vitejs/plugin-vue': + specifier: ^6.0.6 + version: 6.0.7(vite@8.0.16(@types/node@24.13.1))(vue@3.5.35(typescript@6.0.3)) + '@vue/tsconfig': + specifier: ^0.9.1 + version: 0.9.1(typescript@6.0.3)(vue@3.5.35(typescript@6.0.3)) + oxfmt: + specifier: ^0.54.0 + version: 0.54.0 + typescript: + specifier: ~6.0.2 + version: 6.0.3 + vite: + specifier: ^8.0.12 + version: 8.0.16(@types/node@24.13.1) + vue-tsc: + specifier: ^3.2.8 + version: 3.3.4(typescript@6.0.3) + +packages: + + '@alova/shared@1.3.2': + resolution: {integrity: sha512-1XvDLWgYpVZ99MmLl1f3Fw4T6S6pPYk5afz5cwRVjuq8JXEGsDn9IygDKfvRyWqkqCBx7Jif07LIct1O+MVEow==} + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.7': + resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/types@7.29.7': + resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} + engines: {node: '>=6.9.0'} + + '@css-render/plugin-bem@0.15.14': + resolution: {integrity: sha512-QK513CJ7yEQxm/P3EwsI+d+ha8kSOcjGvD6SevM41neEMxdULE+18iuQK6tEChAWMOQNQPLG/Rw3Khb69r5neg==} + peerDependencies: + css-render: ~0.15.14 + + '@css-render/vue3-ssr@0.15.14': + resolution: {integrity: sha512-//8027GSbxE9n3QlD73xFY6z4ZbHbvrOVB7AO6hsmrEzGbg+h2A09HboUyDgu+xsmj7JnvJD39Irt+2D0+iV8g==} + peerDependencies: + vue: ^3.0.11 + + '@emnapi/core@1.10.0': + resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} + + '@emnapi/runtime@1.10.0': + resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} + + '@emnapi/wasi-threads@1.2.1': + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + + '@emotion/hash@0.8.0': + resolution: {integrity: sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@juggle/resize-observer@3.4.0': + resolution: {integrity: sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==} + + '@napi-rs/wasm-runtime@1.1.5': + resolution: {integrity: sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + + '@oxc-project/types@0.133.0': + resolution: {integrity: sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==} + + '@oxfmt/binding-android-arm-eabi@0.54.0': + resolution: {integrity: sha512-NAtpl/SiaeU103e7/OmZw0MvUnsUUopW7hEm/ecegJg7YM0skQaA0IXEZoyTV6NUdiNPupdIUreRqUZTShbn/g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxfmt/binding-android-arm64@0.54.0': + resolution: {integrity: sha512-B4VZfBUlKK1rmMChsssNZbkZjE8+FzG3avMjGgMDwbGxXRoXkoeXiAZ+78Oa+eyDPHvDCiUb4zH/vmCOUSafLQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxfmt/binding-darwin-arm64@0.54.0': + resolution: {integrity: sha512-i02vF75b+ePsQP3tHqSxVYI5S6b8X/xqdPu7/mDHXtpgXLTYXi3jJmfHU0j+dnZZDKaYTx/ioCK7QYJmtiJR2g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxfmt/binding-darwin-x64@0.54.0': + resolution: {integrity: sha512-8VMFvGvooXj7mswkbrhdVZ2/sgiDaBzWpkkbtO+qGDLV4EfJd67nQadHkQC0ZNbaWA9ajXfqI6i7PZLIeDzxEQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxfmt/binding-freebsd-x64@0.54.0': + resolution: {integrity: sha512-0cRHnp43WN1Jrc5s0BdbdKgR1XirdvHy7TAFi3JEsoEVQVJxTXMbpVd76sxXlgRswNMDhVFSJw+y7Eb8mEavFQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxfmt/binding-linux-arm-gnueabihf@0.54.0': + resolution: {integrity: sha512-JyQAk3hK/OEtup7Rw6kZwfdzbKqTVD5jXXb8Xpfay29suwZyfBDMVW/bj4RqEPySYWc6zCp198pOluf8n5uYzg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxfmt/binding-linux-arm-musleabihf@0.54.0': + resolution: {integrity: sha512-qnvLatTpM8vtvjOfcckBOzJjk+n6ce/wwpP8OFeUrD5aNLYcKyWAitwj+Rk3PK9jGanbZvKsJnv14JGQ6XqFdw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxfmt/binding-linux-arm64-gnu@0.54.0': + resolution: {integrity: sha512-SMkhnCzIYZYDk9vw3W/80eeYKmrMpGF0Giuxt4HruFlCH7jEtnPeb3SdQKMfgYi/dgtaf+hZAb5XWPYnxqCQ3w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-arm64-musl@0.54.0': + resolution: {integrity: sha512-QrwJlBFFKnxOd95TAaszpMbZBLzMoYMpGaQTZF8oibacnF5rv8l12IhILhQRPmksWiBqg0YSe2Mnl7ayeJAHSA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-linux-ppc64-gnu@0.54.0': + resolution: {integrity: sha512-WILatiol/TUHTlhod7R09+7Az/XlhKwmY1MHfLZNmewltPWNN/EwxP2rQSHahibZ/cB8gmckEBjBOByD+5bYsQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-riscv64-gnu@0.54.0': + resolution: {integrity: sha512-f05YMG4BH4G8S4ME6UM6fi1MnJ9094mrnvO5Pa4SJlMfWlUM+1/ZWMEF4NnjM7shZAvbHsHRuVYpUo0PHC4P9Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-riscv64-musl@0.54.0': + resolution: {integrity: sha512-UfL+2hj1ClNqcCRT9s8vBU4axDpjxgVxX96G+9DYAYjoc5b0u15CJtn2jgsi9iM+EbGNc5CW1HVRgwVu76UsSA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-linux-s390x-gnu@0.54.0': + resolution: {integrity: sha512-3/XZe931Hka+J6NjnaqJzYpsWWxDTuRdUdwSQHnOuJEgbC+SehIMFJS8hsEjV7LBhVSL2OCnRLvbVW8O97XIyw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-x64-gnu@0.54.0': + resolution: {integrity: sha512-Ik93RlObtu43GbxApafayFjwYE06L6Xr08cSwpBPYbDrLp2ReZx0Jm1DqwRyYRnukUJy+rK2WaEvUQOxdytU9Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-x64-musl@0.54.0': + resolution: {integrity: sha512-yZcakmPlD86CNymknd7KfW+FH+qfbqJH+i0h69CYfV1+KMoVeM9UED+8+TDVoU4haxI0NxY7RPCvRLy3Sqd2Qg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-openharmony-arm64@0.54.0': + resolution: {integrity: sha512-GiVBZNnEZnKu00f1jTg49nomv187d0GQX+O+ocykoLeiaALuEO+swoTehHn9TehTfi7V8H0i0e/yvUjCqnwk1w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxfmt/binding-win32-arm64-msvc@0.54.0': + resolution: {integrity: sha512-J0SSB8Z1Fre2sxRolYcW6Rl1RQmKdQ2hnHyq4YJrfBRiXTObLw4DXnIVraM/UyqGqwOi7yTrQA4VT7DPxlHVKA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxfmt/binding-win32-ia32-msvc@0.54.0': + resolution: {integrity: sha512-O61UDVj8zz6yXJjkHPf05VaMLOXmEF8P5kf/N0W7AQMmd6bcQogl+KJc7rMutKTL524oE9iH32JXZClBFmEQIg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxfmt/binding-win32-x64-msvc@0.54.0': + resolution: {integrity: sha512-1MDpqJPiFqxWtIHas8vkb1VZ7f7eKyTffAwmO8isxQYMaG1OFKsH666BWLeXQLO+IWNfiMssLD55hbR1lIPTqg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/binding-android-arm64@1.0.3': + resolution: {integrity: sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.0.3': + resolution: {integrity: sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.0.3': + resolution: {integrity: sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.0.3': + resolution: {integrity: sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.0.3': + resolution: {integrity: sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.0.3': + resolution: {integrity: sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.0.3': + resolution: {integrity: sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.0.3': + resolution: {integrity: sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.0.3': + resolution: {integrity: sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.0.3': + resolution: {integrity: sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.0.3': + resolution: {integrity: sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.0.3': + resolution: {integrity: sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.0.3': + resolution: {integrity: sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.0.3': + resolution: {integrity: sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.0.3': + resolution: {integrity: sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + + '@tybys/wasm-util@0.10.2': + resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} + + '@types/lodash-es@4.17.12': + resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==} + + '@types/lodash@4.17.24': + resolution: {integrity: sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==} + + '@types/node@24.13.1': + resolution: {integrity: sha512-RSpUJGmvsJ1ZeBehQZFhIdpsz+bIpES0nIQXko4Ybq+N+kX6XvOq3Jo+iJ82FWLdblFq85AsMikd3m35jgezYg==} + + '@vitejs/plugin-vue@6.0.7': + resolution: {integrity: sha512-km+p+XdSz9Sxm5rqUbqcSfZYaAniKxWBj1KURl+Jr7UaPvvX7BmaWMdP69I5rrFDeQGyxAG7NXdc57vz+snhWg==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + vue: ^3.2.25 + + '@volar/language-core@2.4.28': + resolution: {integrity: sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==} + + '@volar/source-map@2.4.28': + resolution: {integrity: sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==} + + '@volar/typescript@2.4.28': + resolution: {integrity: sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==} + + '@vue/compiler-core@3.5.35': + resolution: {integrity: sha512-BUmHaR1J+O+CKZ9uJucdVTEr1LHsdyvv7vG3eNRhK3CczEHeMd/LtsHAuD7PbrxvI2envCY2v7HI1vC1aBRzKw==} + + '@vue/compiler-dom@3.5.35': + resolution: {integrity: sha512-k+bprkXxuqhVajgTx5mUHuir7TwQzUKOWR40ng1ncAqQRPnrLngGGgqVEEhOnTMlc8btHYVKmrP8s5Qyg0hvYA==} + + '@vue/compiler-sfc@3.5.35': + resolution: {integrity: sha512-G5VPMcXTSywXBgtFOZOnHKBxKSrwXUcvY1iaF5/hRcy7t0J6CH/d8ha9F4nzi00Fax1eLV0QHM7v4mQu68jydw==} + + '@vue/compiler-ssr@3.5.35': + resolution: {integrity: sha512-rGhAeXgdM7/ffTJGXT69rCCdTmjDewnFuUZfBQQHTdcEBeWdT5HCGY60y2ytLJr9/Dsu7IntUi5z/w0h6Rjnzw==} + + '@vue/language-core@3.3.4': + resolution: {integrity: sha512-IuHqQ5zGGOE7CXP72VX6A42IVeIzYv4WAhO6arej11TRNqtdZfGyH8Yr2FOCaDX0dSQG+JwULLoFHGY1igYVjQ==} + + '@vue/reactivity@3.5.35': + resolution: {integrity: sha512-tVc+SsHConvh/Lz64qq1pP3rYArBmK42xonovEcxY74SQtvctZodG/zhq54P5dr38cVuw25d27cPNRdlMidpGQ==} + + '@vue/runtime-core@3.5.35': + resolution: {integrity: sha512-A/xFNX9loIcWDygeQuNCfKuh0CoYBzxhqEMNah5TSFg9Z53DrFYEN2qi5CU9necjM1OWYegYREUTHmXTmhfXtg==} + + '@vue/runtime-dom@3.5.35': + resolution: {integrity: sha512-odrJ1C391dbGnyDRh8U+rnP7J2amIEzfmRk5vXy7xi3aZhEXofTvpi0T4HJb6jlNqQZTNPR5MPHSB3RHNkIORA==} + + '@vue/server-renderer@3.5.35': + resolution: {integrity: sha512-NkebSOYdB97wi8OQcO3HqzZSlymJi/aWsN/7h74OSVhRTm6qGs3Jp3e0rCXynmWwSlKeRrnlIug+ilYoHBmQDA==} + peerDependencies: + vue: 3.5.35 + + '@vue/shared@3.5.35': + resolution: {integrity: sha512-zSbjL7gRXwks2ZQLRGCajBtBXEOXW9Ddhn/HvSdrGkE2dqGnumzW8XtusRrxrE9LvqtiqDXQ+A60Hp6mvdYxfA==} + + '@vue/tsconfig@0.9.1': + resolution: {integrity: sha512-buvjm+9NzLCJL29KY1j1991YYJ5e6275OiK+G4jtmfIb+z4POywbdm0wXusT9adVWqe0xqg70TbI7+mRx4uU9w==} + peerDependencies: + typescript: '>= 5.8' + vue: ^3.4.0 + peerDependenciesMeta: + typescript: + optional: true + vue: + optional: true + + alien-signals@3.2.1: + resolution: {integrity: sha512-I8FjmltrfnDFoZedi5CG8DghVYNhzb/Ijluz7tCSJH0xpd0484Kowhbb1XDYOxfJpU1p5wnM2X54dA+IfGyD1g==} + + alova@3.5.1: + resolution: {integrity: sha512-avrWPyFFWW51YLoy0S3OleNw1BV0GqNI+DSdWHfFbAoKZp80cXCCc7OtjA6OWeyhCOMglUMwo9O8j5huwnzFtQ==} + engines: {node: '>= 18.0.0'} + + async-validator@4.2.5: + resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==} + + css-render@0.15.14: + resolution: {integrity: sha512-9nF4PdUle+5ta4W5SyZdLCCmFd37uVimSjg1evcTqKJCyvCEEj12WKzOSBNak6r4im4J4iYXKH1OWpUV5LBYFg==} + + csstype@3.0.11: + resolution: {integrity: sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + date-fns-tz@3.2.0: + resolution: {integrity: sha512-sg8HqoTEulcbbbVXeg84u5UnlsQa8GS5QXMqjjYIhS4abEVVKIUwe0/l/UhrZdKaL/W5eWZNlbTeEIiOXTcsBQ==} + peerDependencies: + date-fns: ^3.0.0 || ^4.0.0 + + date-fns@4.4.0: + resolution: {integrity: sha512-+1UMbeh68lH1SegH83CGWwpb6OHHbpSgr3+s5Eww5M4CAgswBpoWS0AjTOfEJ33HiYKz1hdj/KTFprzXHmq/6w==} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + entities@7.0.1: + resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} + engines: {node: '>=0.12'} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + evtd@0.2.4: + resolution: {integrity: sha512-qaeGN5bx63s/AXgQo8gj6fBkxge+OoLddLniox5qtLAEY5HSnuSlISXVPxnSae1dWblvTh4/HoMIB+mbMsvZzw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + highlight.js@11.11.1: + resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==} + engines: {node: '>=12.0.0'} + + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + + lodash-es@4.18.1: + resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==} + + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + muggle-string@0.4.1: + resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} + + naive-ui@2.44.1: + resolution: {integrity: sha512-reo8Esw0p58liZwbUutC7meW24Xbn3EwNv91zReWKm2W4JPu+zfgJRn/F7aO0BFmvN+h2brA2M5lRvYqLq4kuA==} + engines: {node: '>=20'} + peerDependencies: + vue: ^3.0.0 + + nanoid@3.3.12: + resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + oxfmt@0.54.0: + resolution: {integrity: sha512-DjnMwn7smSLF+Mc2+pRItnuPftm/dkUFpY/d4+33y9TfKrsHZo8GLhmUg9BrOIUEy94Rlom1Q11N6vuhE+e0oQ==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + svelte: ^5.0.0 + vite-plus: '*' + peerDependenciesMeta: + svelte: + optional: true + vite-plus: + optional: true + + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + postcss@8.5.15: + resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} + engines: {node: ^10 || ^12 || >=14} + + rate-limiter-flexible@5.0.5: + resolution: {integrity: sha512-+/dSQfo+3FYwYygUs/V2BBdwGa9nFtakDwKt4l0bnvNB53TNT++QSFewwHX9qXrZJuMe9j+TUaU21lm5ARgqdQ==} + + rolldown@1.0.3: + resolution: {integrity: sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + seemly@0.3.10: + resolution: {integrity: sha512-2+SMxtG1PcsL0uyhkumlOU6Qo9TAQ/WyH7tthnPIOQB05/12jz9naq6GZ6iZ6ApVsO3rr2gsnTf3++OV63kE1Q==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + tinypool@2.1.0: + resolution: {integrity: sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==} + engines: {node: ^20.0.0 || >=22.0.0} + + treemate@0.3.11: + resolution: {integrity: sha512-M8RGFoKtZ8dF+iwJfAJTOH/SM4KluKOKRJpjCMhI8bG3qB74zrFoArKZ62ll0Fr3mqkMJiQOmWYkdYgDeITYQg==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@7.18.2: + resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} + + vdirs@0.1.8: + resolution: {integrity: sha512-H9V1zGRLQZg9b+GdMk8MXDN2Lva0zx72MPahDKc30v+DtwKjfyOSXWRIX4t2mhDubM1H09gPhWeth/BJWPHGUw==} + peerDependencies: + vue: ^3.0.11 + + vite@8.0.16: + resolution: {integrity: sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.1.18 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vooks@0.2.12: + resolution: {integrity: sha512-iox0I3RZzxtKlcgYaStQYKEzWWGAduMmq+jS7OrNdQo1FgGfPMubGL3uGHOU9n97NIvfFDBGnpSvkWyb/NSn/Q==} + peerDependencies: + vue: ^3.0.0 + + vscode-uri@3.1.0: + resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} + + vue-tsc@3.3.4: + resolution: {integrity: sha512-XA/JqmQwS2GZmfgpjOEGdrKwaTSEuPwxpHa7/t6f4yiGrJb3gVHTPb9wBfByMNZwQ+xDXs41b8gaS2DKsOozUw==} + hasBin: true + peerDependencies: + typescript: '>=5.0.0' + + vue@3.5.35: + resolution: {integrity: sha512-cx89fnr+0kVGHiNFG6y6s0bdjypJRFNZn6x3WPstNdQR1bi1mbB7h4v5IBGTsPJU3nK1+0Iqj3Zf+hZWMieR4Q==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + vueuc@0.4.65: + resolution: {integrity: sha512-lXuMl+8gsBmruudfxnMF9HW4be8rFziylXFu1VHVNbLVhRTXXV4njvpRuJapD/8q+oFEMSfQMH16E/85VoWRyQ==} + peerDependencies: + vue: ^3.0.11 + +snapshots: + + '@alova/shared@1.3.2': {} + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/parser@7.29.7': + dependencies: + '@babel/types': 7.29.7 + + '@babel/types@7.29.7': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@css-render/plugin-bem@0.15.14(css-render@0.15.14)': + dependencies: + css-render: 0.15.14 + + '@css-render/vue3-ssr@0.15.14(vue@3.5.35(typescript@6.0.3))': + dependencies: + vue: 3.5.35(typescript@6.0.3) + + '@emnapi/core@1.10.0': + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.10.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@emotion/hash@0.8.0': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@juggle/resize-observer@3.4.0': {} + + '@napi-rs/wasm-runtime@1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@tybys/wasm-util': 0.10.2 + optional: true + + '@oxc-project/types@0.133.0': {} + + '@oxfmt/binding-android-arm-eabi@0.54.0': + optional: true + + '@oxfmt/binding-android-arm64@0.54.0': + optional: true + + '@oxfmt/binding-darwin-arm64@0.54.0': + optional: true + + '@oxfmt/binding-darwin-x64@0.54.0': + optional: true + + '@oxfmt/binding-freebsd-x64@0.54.0': + optional: true + + '@oxfmt/binding-linux-arm-gnueabihf@0.54.0': + optional: true + + '@oxfmt/binding-linux-arm-musleabihf@0.54.0': + optional: true + + '@oxfmt/binding-linux-arm64-gnu@0.54.0': + optional: true + + '@oxfmt/binding-linux-arm64-musl@0.54.0': + optional: true + + '@oxfmt/binding-linux-ppc64-gnu@0.54.0': + optional: true + + '@oxfmt/binding-linux-riscv64-gnu@0.54.0': + optional: true + + '@oxfmt/binding-linux-riscv64-musl@0.54.0': + optional: true + + '@oxfmt/binding-linux-s390x-gnu@0.54.0': + optional: true + + '@oxfmt/binding-linux-x64-gnu@0.54.0': + optional: true + + '@oxfmt/binding-linux-x64-musl@0.54.0': + optional: true + + '@oxfmt/binding-openharmony-arm64@0.54.0': + optional: true + + '@oxfmt/binding-win32-arm64-msvc@0.54.0': + optional: true + + '@oxfmt/binding-win32-ia32-msvc@0.54.0': + optional: true + + '@oxfmt/binding-win32-x64-msvc@0.54.0': + optional: true + + '@rolldown/binding-android-arm64@1.0.3': + optional: true + + '@rolldown/binding-darwin-arm64@1.0.3': + optional: true + + '@rolldown/binding-darwin-x64@1.0.3': + optional: true + + '@rolldown/binding-freebsd-x64@1.0.3': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.0.3': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.0.3': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.3': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.3': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.3': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.3': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.3': + optional: true + + '@rolldown/pluginutils@1.0.1': {} + + '@tybys/wasm-util@0.10.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/lodash-es@4.17.12': + dependencies: + '@types/lodash': 4.17.24 + + '@types/lodash@4.17.24': {} + + '@types/node@24.13.1': + dependencies: + undici-types: 7.18.2 + + '@vitejs/plugin-vue@6.0.7(vite@8.0.16(@types/node@24.13.1))(vue@3.5.35(typescript@6.0.3))': + dependencies: + '@rolldown/pluginutils': 1.0.1 + vite: 8.0.16(@types/node@24.13.1) + vue: 3.5.35(typescript@6.0.3) + + '@volar/language-core@2.4.28': + dependencies: + '@volar/source-map': 2.4.28 + + '@volar/source-map@2.4.28': {} + + '@volar/typescript@2.4.28': + dependencies: + '@volar/language-core': 2.4.28 + path-browserify: 1.0.1 + vscode-uri: 3.1.0 + + '@vue/compiler-core@3.5.35': + dependencies: + '@babel/parser': 7.29.7 + '@vue/shared': 3.5.35 + entities: 7.0.1 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.35': + dependencies: + '@vue/compiler-core': 3.5.35 + '@vue/shared': 3.5.35 + + '@vue/compiler-sfc@3.5.35': + dependencies: + '@babel/parser': 7.29.7 + '@vue/compiler-core': 3.5.35 + '@vue/compiler-dom': 3.5.35 + '@vue/compiler-ssr': 3.5.35 + '@vue/shared': 3.5.35 + estree-walker: 2.0.2 + magic-string: 0.30.21 + postcss: 8.5.15 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.35': + dependencies: + '@vue/compiler-dom': 3.5.35 + '@vue/shared': 3.5.35 + + '@vue/language-core@3.3.4': + dependencies: + '@volar/language-core': 2.4.28 + '@vue/compiler-dom': 3.5.35 + '@vue/shared': 3.5.35 + alien-signals: 3.2.1 + muggle-string: 0.4.1 + path-browserify: 1.0.1 + picomatch: 4.0.4 + + '@vue/reactivity@3.5.35': + dependencies: + '@vue/shared': 3.5.35 + + '@vue/runtime-core@3.5.35': + dependencies: + '@vue/reactivity': 3.5.35 + '@vue/shared': 3.5.35 + + '@vue/runtime-dom@3.5.35': + dependencies: + '@vue/reactivity': 3.5.35 + '@vue/runtime-core': 3.5.35 + '@vue/shared': 3.5.35 + csstype: 3.2.3 + + '@vue/server-renderer@3.5.35(vue@3.5.35(typescript@6.0.3))': + dependencies: + '@vue/compiler-ssr': 3.5.35 + '@vue/shared': 3.5.35 + vue: 3.5.35(typescript@6.0.3) + + '@vue/shared@3.5.35': {} + + '@vue/tsconfig@0.9.1(typescript@6.0.3)(vue@3.5.35(typescript@6.0.3))': + optionalDependencies: + typescript: 6.0.3 + vue: 3.5.35(typescript@6.0.3) + + alien-signals@3.2.1: {} + + alova@3.5.1: + dependencies: + '@alova/shared': 1.3.2 + rate-limiter-flexible: 5.0.5 + + async-validator@4.2.5: {} + + css-render@0.15.14: + dependencies: + '@emotion/hash': 0.8.0 + csstype: 3.0.11 + + csstype@3.0.11: {} + + csstype@3.2.3: {} + + date-fns-tz@3.2.0(date-fns@4.4.0): + dependencies: + date-fns: 4.4.0 + + date-fns@4.4.0: {} + + detect-libc@2.1.2: {} + + entities@7.0.1: {} + + estree-walker@2.0.2: {} + + evtd@0.2.4: {} + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + fsevents@2.3.3: + optional: true + + highlight.js@11.11.1: {} + + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + + lodash-es@4.18.1: {} + + lodash@4.18.1: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + muggle-string@0.4.1: {} + + naive-ui@2.44.1(vue@3.5.35(typescript@6.0.3)): + dependencies: + '@css-render/plugin-bem': 0.15.14(css-render@0.15.14) + '@css-render/vue3-ssr': 0.15.14(vue@3.5.35(typescript@6.0.3)) + '@types/lodash': 4.17.24 + '@types/lodash-es': 4.17.12 + async-validator: 4.2.5 + css-render: 0.15.14 + csstype: 3.2.3 + date-fns: 4.4.0 + date-fns-tz: 3.2.0(date-fns@4.4.0) + evtd: 0.2.4 + highlight.js: 11.11.1 + lodash: 4.18.1 + lodash-es: 4.18.1 + seemly: 0.3.10 + treemate: 0.3.11 + vdirs: 0.1.8(vue@3.5.35(typescript@6.0.3)) + vooks: 0.2.12(vue@3.5.35(typescript@6.0.3)) + vue: 3.5.35(typescript@6.0.3) + vueuc: 0.4.65(vue@3.5.35(typescript@6.0.3)) + + nanoid@3.3.12: {} + + oxfmt@0.54.0: + dependencies: + tinypool: 2.1.0 + optionalDependencies: + '@oxfmt/binding-android-arm-eabi': 0.54.0 + '@oxfmt/binding-android-arm64': 0.54.0 + '@oxfmt/binding-darwin-arm64': 0.54.0 + '@oxfmt/binding-darwin-x64': 0.54.0 + '@oxfmt/binding-freebsd-x64': 0.54.0 + '@oxfmt/binding-linux-arm-gnueabihf': 0.54.0 + '@oxfmt/binding-linux-arm-musleabihf': 0.54.0 + '@oxfmt/binding-linux-arm64-gnu': 0.54.0 + '@oxfmt/binding-linux-arm64-musl': 0.54.0 + '@oxfmt/binding-linux-ppc64-gnu': 0.54.0 + '@oxfmt/binding-linux-riscv64-gnu': 0.54.0 + '@oxfmt/binding-linux-riscv64-musl': 0.54.0 + '@oxfmt/binding-linux-s390x-gnu': 0.54.0 + '@oxfmt/binding-linux-x64-gnu': 0.54.0 + '@oxfmt/binding-linux-x64-musl': 0.54.0 + '@oxfmt/binding-openharmony-arm64': 0.54.0 + '@oxfmt/binding-win32-arm64-msvc': 0.54.0 + '@oxfmt/binding-win32-ia32-msvc': 0.54.0 + '@oxfmt/binding-win32-x64-msvc': 0.54.0 + + path-browserify@1.0.1: {} + + picocolors@1.1.1: {} + + picomatch@4.0.4: {} + + postcss@8.5.15: + dependencies: + nanoid: 3.3.12 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + rate-limiter-flexible@5.0.5: {} + + rolldown@1.0.3: + dependencies: + '@oxc-project/types': 0.133.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.3 + '@rolldown/binding-darwin-arm64': 1.0.3 + '@rolldown/binding-darwin-x64': 1.0.3 + '@rolldown/binding-freebsd-x64': 1.0.3 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.3 + '@rolldown/binding-linux-arm64-gnu': 1.0.3 + '@rolldown/binding-linux-arm64-musl': 1.0.3 + '@rolldown/binding-linux-ppc64-gnu': 1.0.3 + '@rolldown/binding-linux-s390x-gnu': 1.0.3 + '@rolldown/binding-linux-x64-gnu': 1.0.3 + '@rolldown/binding-linux-x64-musl': 1.0.3 + '@rolldown/binding-openharmony-arm64': 1.0.3 + '@rolldown/binding-wasm32-wasi': 1.0.3 + '@rolldown/binding-win32-arm64-msvc': 1.0.3 + '@rolldown/binding-win32-x64-msvc': 1.0.3 + + seemly@0.3.10: {} + + source-map-js@1.2.1: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + tinypool@2.1.0: {} + + treemate@0.3.11: {} + + tslib@2.8.1: + optional: true + + typescript@6.0.3: {} + + undici-types@7.18.2: {} + + vdirs@0.1.8(vue@3.5.35(typescript@6.0.3)): + dependencies: + evtd: 0.2.4 + vue: 3.5.35(typescript@6.0.3) + + vite@8.0.16(@types/node@24.13.1): + dependencies: + lightningcss: 1.32.0 + picomatch: 4.0.4 + postcss: 8.5.15 + rolldown: 1.0.3 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 24.13.1 + fsevents: 2.3.3 + + vooks@0.2.12(vue@3.5.35(typescript@6.0.3)): + dependencies: + evtd: 0.2.4 + vue: 3.5.35(typescript@6.0.3) + + vscode-uri@3.1.0: {} + + vue-tsc@3.3.4(typescript@6.0.3): + dependencies: + '@volar/typescript': 2.4.28 + '@vue/language-core': 3.3.4 + typescript: 6.0.3 + + vue@3.5.35(typescript@6.0.3): + dependencies: + '@vue/compiler-dom': 3.5.35 + '@vue/compiler-sfc': 3.5.35 + '@vue/runtime-dom': 3.5.35 + '@vue/server-renderer': 3.5.35(vue@3.5.35(typescript@6.0.3)) + '@vue/shared': 3.5.35 + optionalDependencies: + typescript: 6.0.3 + + vueuc@0.4.65(vue@3.5.35(typescript@6.0.3)): + dependencies: + '@css-render/vue3-ssr': 0.15.14(vue@3.5.35(typescript@6.0.3)) + '@juggle/resize-observer': 3.4.0 + css-render: 0.15.14 + evtd: 0.2.4 + seemly: 0.3.10 + vdirs: 0.1.8(vue@3.5.35(typescript@6.0.3)) + vooks: 0.2.12(vue@3.5.35(typescript@6.0.3)) + vue: 3.5.35(typescript@6.0.3) diff --git a/code/ai/ai-vue/public/favicon.svg b/code/ai/ai-vue/public/favicon.svg new file mode 100644 index 00000000..6893eb13 --- /dev/null +++ b/code/ai/ai-vue/public/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/code/ai/ai-vue/public/icons.svg b/code/ai/ai-vue/public/icons.svg new file mode 100644 index 00000000..e9522193 --- /dev/null +++ b/code/ai/ai-vue/public/icons.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/ai/ai-vue/src/App.vue b/code/ai/ai-vue/src/App.vue new file mode 100644 index 00000000..637fea95 --- /dev/null +++ b/code/ai/ai-vue/src/App.vue @@ -0,0 +1,425 @@ + + + + + diff --git a/code/ai/ai-vue/src/api/file.ts b/code/ai/ai-vue/src/api/file.ts new file mode 100644 index 00000000..978c8202 --- /dev/null +++ b/code/ai/ai-vue/src/api/file.ts @@ -0,0 +1,62 @@ +import { createAlova } from "alova"; +import adapterFetch from "alova/fetch"; + +export type UploadedFile = { + originalName: string; + fileName: string; + url: string; + size: number; + contentType: string | null; +}; + +export type RecognitionRequest = { + url: string; + type: string; +}; + +export type RecognitionResponse = { + url: string; + type: string; + ppStructureResult: unknown; +}; + +const defaultApiBaseUrl = "http://192.168.1.137:8082"; + +function normalizeBaseUrl(baseUrl: string | undefined) { + const value = baseUrl?.trim() || defaultApiBaseUrl; + return value.endsWith("/") ? value.slice(0, -1) : value; +} + +async function readErrorMessage(response: Response) { + const fallback = `请求失败,状态码 ${response.status}`; + + try { + const data = await response.json(); + return data.detail || data.message || data.error || fallback; + } catch { + return fallback; + } +} + +const http = createAlova({ + baseURL: normalizeBaseUrl(import.meta.env.VITE_API_BASE_URL), + requestAdapter: adapterFetch(), + responded: async (response) => { + if (!response.ok) { + throw new Error(await readErrorMessage(response)); + } + + return response.json(); + }, +}); + +export function uploadFile(file: File) { + const formData = new FormData(); + formData.append("file", file); + + return http.Post("/api/files/upload", formData).send(); +} + +export function recognizeDocument(data: RecognitionRequest) { + return http.Post("/api/recognitions/start", data).send(); +} diff --git a/code/ai/ai-vue/src/assets/hero.png b/code/ai/ai-vue/src/assets/hero.png new file mode 100644 index 00000000..02251f4b Binary files /dev/null and b/code/ai/ai-vue/src/assets/hero.png differ diff --git a/code/ai/ai-vue/src/assets/vite.svg b/code/ai/ai-vue/src/assets/vite.svg new file mode 100644 index 00000000..5101b674 --- /dev/null +++ b/code/ai/ai-vue/src/assets/vite.svg @@ -0,0 +1 @@ +Vite diff --git a/code/ai/ai-vue/src/assets/vue.svg b/code/ai/ai-vue/src/assets/vue.svg new file mode 100644 index 00000000..770e9d33 --- /dev/null +++ b/code/ai/ai-vue/src/assets/vue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/code/ai/ai-vue/src/components/HelloWorld.vue b/code/ai/ai-vue/src/components/HelloWorld.vue new file mode 100644 index 00000000..2fe0d76e --- /dev/null +++ b/code/ai/ai-vue/src/components/HelloWorld.vue @@ -0,0 +1,93 @@ + + + diff --git a/code/ai/ai-vue/src/main.ts b/code/ai/ai-vue/src/main.ts new file mode 100644 index 00000000..f3b84632 --- /dev/null +++ b/code/ai/ai-vue/src/main.ts @@ -0,0 +1,6 @@ +import { createApp } from "vue"; +import "./style.css"; +import App from "./App.vue"; +import naive from "naive-ui"; + +createApp(App).use(naive).mount("#app"); diff --git a/code/ai/ai-vue/src/style.css b/code/ai/ai-vue/src/style.css new file mode 100644 index 00000000..7c1096be --- /dev/null +++ b/code/ai/ai-vue/src/style.css @@ -0,0 +1,27 @@ +* { + box-sizing: border-box; +} + +html, +body, +#app { + width: 100%; + min-width: 320px; + height: 100%; + margin: 0; + overflow: hidden; +} + +body { + font-family: + Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", + sans-serif; + background: #f4f6f8; +} + +button, +input, +textarea, +select { + font: inherit; +} diff --git a/code/ai/ai-vue/tsconfig.app.json b/code/ai/ai-vue/tsconfig.app.json new file mode 100644 index 00000000..5c750c5d --- /dev/null +++ b/code/ai/ai-vue/tsconfig.app.json @@ -0,0 +1,14 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "types": ["vite/client"], + + /* Linting */ + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"] +} diff --git a/code/ai/ai-vue/tsconfig.json b/code/ai/ai-vue/tsconfig.json new file mode 100644 index 00000000..d32ff682 --- /dev/null +++ b/code/ai/ai-vue/tsconfig.json @@ -0,0 +1,4 @@ +{ + "files": [], + "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }] +} diff --git a/code/ai/ai-vue/tsconfig.node.json b/code/ai/ai-vue/tsconfig.node.json new file mode 100644 index 00000000..d3c52ea6 --- /dev/null +++ b/code/ai/ai-vue/tsconfig.node.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "es2023", + "lib": ["ES2023"], + "module": "esnext", + "types": ["node"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["vite.config.ts"] +} diff --git a/code/ai/ai-vue/vite.config.ts b/code/ai/ai-vue/vite.config.ts new file mode 100644 index 00000000..b66b21bc --- /dev/null +++ b/code/ai/ai-vue/vite.config.ts @@ -0,0 +1,24 @@ +import { defineConfig, loadEnv } from "vite"; +import vue from "@vitejs/plugin-vue"; + +// https://vite.dev/config/ +export default defineConfig(({ mode }) => { + const env = loadEnv(mode, process.cwd(), ""); + const apiBaseUrl = env.VITE_API_BASE_URL || "http://192.168.1.137:8082"; + + return { + plugins: [vue()], + server: { + proxy: { + "/api": { + target: apiBaseUrl, + changeOrigin: true, + }, + "/uploads": { + target: apiBaseUrl, + changeOrigin: true, + }, + }, + }, + }; +}); diff --git a/code/ai/uploads/9a6506af761a427490b5392dc18c8cc5.jpg b/code/ai/uploads/9a6506af761a427490b5392dc18c8cc5.jpg new file mode 100644 index 00000000..39283c9c Binary files /dev/null and b/code/ai/uploads/9a6506af761a427490b5392dc18c8cc5.jpg differ diff --git a/code/ai/uploads/ba1e6df6b8e04f0baaad991b33602b1b.pdf b/code/ai/uploads/ba1e6df6b8e04f0baaad991b33602b1b.pdf new file mode 100644 index 00000000..831cb7a1 Binary files /dev/null and b/code/ai/uploads/ba1e6df6b8e04f0baaad991b33602b1b.pdf differ diff --git a/code/docusaurus/docs/resources.mdx b/code/docusaurus/docs/resources.mdx index 390b0a74..ece10618 100644 --- a/code/docusaurus/docs/resources.mdx +++ b/code/docusaurus/docs/resources.mdx @@ -6,512 +6,6 @@ hide_table_of_contents: true --- import SearchableLinkGrid from '@site/src/components/Link/SearchableLinkGrid'; +import resourceGroups from '@site/src/data/resources'; - + diff --git a/code/docusaurus/docusaurus.config.ts b/code/docusaurus/docusaurus.config.ts index 7f4aa0af..4b1d1ebe 100644 --- a/code/docusaurus/docusaurus.config.ts +++ b/code/docusaurus/docusaurus.config.ts @@ -108,11 +108,6 @@ const config: Config = { position: 'left', sidebarId: 'DatabaseSidebar', }, - { - to: '/docs/resources', - label: '资源导航', - position: 'left', - }, { to: '/blog', label: '博客', diff --git a/code/docusaurus/src/components/Link/LinkCard.tsx b/code/docusaurus/src/components/Link/LinkCard.tsx index 4798c444..d57b4812 100644 --- a/code/docusaurus/src/components/Link/LinkCard.tsx +++ b/code/docusaurus/src/components/Link/LinkCard.tsx @@ -1,11 +1,19 @@ import styles from './styles.module.css' +export type LinkCardTagType = 'default' | 'success' | 'warning' | 'error' | 'info' + +export interface LinkCardTag { + label: string + type?: LinkCardTagType +} + export interface LinkCardProps { title: string description: string href: string icon?: string note?: string + tags?: LinkCardTag[] } function getHostname(href: string) { @@ -23,6 +31,7 @@ export default function LinkCard({ href, icon, note, + tags, }: LinkCardProps) { const hostname = getHostname(href) const favicon = icon || (hostname ? `https://www.google.com/s2/favicons?domain=${hostname}&sz=64` : undefined) @@ -34,17 +43,29 @@ export default function LinkCard({ target="_blank" rel="noopener noreferrer" > - + {favicon ? : {title.charAt(0)}} - {title} + {title} {description} {hostname && {hostname}} + {tags && tags.length > 0 && ( + + {tags.map((tag, index) => ( + + {tag.label} + + ))} + + )} {note && ( - - 备注 + + 备注: {note} )} diff --git a/code/docusaurus/src/components/Link/LinkGrid.tsx b/code/docusaurus/src/components/Link/LinkGrid.tsx index 561b40e6..53f782af 100644 --- a/code/docusaurus/src/components/Link/LinkGrid.tsx +++ b/code/docusaurus/src/components/Link/LinkGrid.tsx @@ -1,5 +1,4 @@ import React from 'react' -import styles from './styles.module.css' interface Props { children: React.ReactNode @@ -7,7 +6,7 @@ interface Props { export default function LinkGrid({ children }: Props) { return ( -
+
{children}
) diff --git a/code/docusaurus/src/components/Link/SearchableLinkGrid.tsx b/code/docusaurus/src/components/Link/SearchableLinkGrid.tsx index 43163ae2..618d64ce 100644 --- a/code/docusaurus/src/components/Link/SearchableLinkGrid.tsx +++ b/code/docusaurus/src/components/Link/SearchableLinkGrid.tsx @@ -101,12 +101,6 @@ export default function SearchableLinkGrid({ groups }: SearchableLinkGridProps)