Compare commits
217 Commits
v1.7.0
...
v2.0.2-RC3
| Author | SHA1 | Date | |
|---|---|---|---|
| 3b9f137061 | |||
| 897fc8c4d4 | |||
| 2758e23381 | |||
| 9d9f5d06a8 | |||
| c589b59dd5 | |||
| dc84431768 | |||
| 0705ea6bae | |||
| c52e2eeb1f | |||
| 58dbe2c027 | |||
| 88c288fdda | |||
| f5e25dcb8d | |||
| e06311a514 | |||
| cb97394211 | |||
| dda51232b9 | |||
| a00cb2d38c | |||
| f92a0fe140 | |||
| b575fe893b | |||
| 54b1b35df5 | |||
| 06a0f742f2 | |||
| 3a5a15fe77 | |||
| 7123326515 | |||
| 4aa52a6b52 | |||
| 9234d80246 | |||
| 10be96814e | |||
| a6d867e4cc | |||
| 4b8de1897b | |||
| 09eaa5b7e3 | |||
| 2746bf5b45 | |||
| f9941827b1 | |||
| 48b4622aca | |||
| 2a442d333c | |||
| 84204a718a | |||
| 5be5c84a26 | |||
| a218924ba0 | |||
| 316417961a | |||
| b1da65fbe4 | |||
| 0aca063077 | |||
| 11091244f6 | |||
| d1a4092c61 | |||
| 11c80384f1 | |||
| ab236f2d6a | |||
| 97924574c4 | |||
| e3831b6f52 | |||
| c93f3bc5c3 | |||
| 278fdfd8c3 | |||
| 2836d0ea81 | |||
| ac7bdda074 | |||
| 057679a2bf | |||
| 5d923f8ca3 | |||
| 7d0714dd1a | |||
| 8dd55af6cd | |||
| 22c2a5c276 | |||
| f346bcc8fa | |||
| dc71f4a292 | |||
| dea18f997c | |||
| 71d11b6205 | |||
| 9da00a807d | |||
| cfc3e54f30 | |||
| 2dbe594da2 | |||
| 36b548140e | |||
| 3c5c47b91b | |||
| 6fc25c69f4 | |||
| ff997cb014 | |||
| 09599e4c20 | |||
| 844c73cd7f | |||
| d6cb1b119f | |||
| 52880f9d5a | |||
| 659d1f6ad9 | |||
| 7952075ce5 | |||
| 52d057ef18 | |||
| 869b040e92 | |||
| 0db88d63e3 | |||
| 92994b4997 | |||
| 638fb082c5 | |||
| fca80abbaf | |||
| a6bfd402b6 | |||
| a55b4c65d2 | |||
| 5ec9d8b623 | |||
| ff6b5736c3 | |||
| d47d7cc452 | |||
| 90022ce039 | |||
| 50a96809d8 | |||
| a08c124748 | |||
| b522a168e3 | |||
| e5cb38bd74 | |||
| 7dfb8b320e | |||
| 34c1e8a38a | |||
| 4fd96675f3 | |||
| a5edc4fc01 | |||
| 2a99d234ea | |||
| 8b61cad014 | |||
| bdd1949a96 | |||
| 9ea13af860 | |||
| b83d0214c7 | |||
| ef54781b77 | |||
| cc1567ac4b | |||
| f403eadb2c | |||
| 94c030829d | |||
| ce5aadf30d | |||
| 9fbf9e4ff4 | |||
| dc7f752128 | |||
| c1549d2909 | |||
| c509723f29 | |||
| 06e9d437e0 | |||
| 2d02452e17 | |||
| 630ac20a38 | |||
| f3dddfeb0e | |||
| 86c2d64dbf | |||
| 73cfbace70 | |||
| 18b76f3e23 | |||
| 6369056914 | |||
| 128c44f5a3 | |||
| 3264fdd1d9 | |||
| c2a0bee7dc | |||
| 9efa9471bd | |||
| f05f1eb80d | |||
| d65e257f92 | |||
| 8b8550e9ca | |||
| 81511e386a | |||
| 60f8cd1803 | |||
| 0bb33e7ea4 | |||
| 94de21361e | |||
| f0636d4369 | |||
| 0d7979e901 | |||
| 3cd3e4a888 | |||
| 29ec29bde7 | |||
| 6b834c745a | |||
| 5be6ec9e71 | |||
| 3d5465334c | |||
| f6d30a5b57 | |||
| d773b86906 | |||
| 1fbb802e8f | |||
| 5ff6106d37 | |||
| 26d868b123 | |||
| d7d692bb37 | |||
| d5c07f18ad | |||
| a1f0b0963b | |||
| 57f31ec7eb | |||
| c8908b7b34 | |||
| 42f8947e7c | |||
| 61d9bf34d3 | |||
| 9e236f65a3 | |||
| 762e8a739d | |||
| 78714e83cf | |||
| aa7e19b896 | |||
| cf7f601cb0 | |||
| c8881889c8 | |||
| 462931a309 | |||
| f92d3a3481 | |||
| 92d853ce3c | |||
| 2ae309abc3 | |||
| b5ae2d0351 | |||
| d3a29e7ad9 | |||
| 0bad2c7249 | |||
| 1c2c733190 | |||
| ffa8dc2bb8 | |||
| 7a4b57d1ff | |||
| d87726bd6e | |||
| 498c142198 | |||
| 3d43700556 | |||
| 762773cedb | |||
| c2a62804b0 | |||
| 9ef55ca07c | |||
| 3000e3aec0 | |||
| 59408774fb | |||
| e35b955159 | |||
| 3ddec99837 | |||
| fbb877ab11 | |||
| 216786a6de | |||
| dd836497e3 | |||
| 8412ccdc12 | |||
| 8f5c164e38 | |||
| c82f0a4fdd | |||
| f5600865e0 | |||
| 82a1127c64 | |||
| 0065e355aa | |||
| bf2127dbd0 | |||
| 5fa638a3e1 | |||
| e1255373cb | |||
| 4950009248 | |||
| 62e765f70a | |||
| 74654f2703 | |||
| 92de2ef16f | |||
| bb60dca5eb | |||
| 480474d7be | |||
| d15d55abe3 | |||
| 2b64983464 | |||
| c0a9f3155a | |||
| f86e98167f | |||
| 9b64d7d64d | |||
| 4609e200ae | |||
| 8b739c5a59 | |||
| 27cd77358c | |||
| 1418745b00 | |||
| 9bb87bf8d6 | |||
| 59df184bab | |||
| 7a1c96d2cb | |||
| d468e2ba8d | |||
| 57ba299543 | |||
| 48b1de8314 | |||
| 8e395d421e | |||
| 740ac61bb2 | |||
| 6660cdf6e2 | |||
| 670aa32325 | |||
| 5e705a124b | |||
| 9540ef6005 | |||
| f723d120d9 | |||
| fc2a1d8afe | |||
| bc629db132 | |||
| 0777291e80 | |||
| ce053ccfa6 | |||
| a15dd41550 | |||
| 09b6b34b05 | |||
| 9a7eb0f450 | |||
| 54e602c5e6 | |||
| 157dbfd08a | |||
| 666ab706a6 |
@@ -0,0 +1,36 @@
|
||||
# Installation
|
||||
INSTALL_PORT=80
|
||||
AUTO_INSTALL=false
|
||||
|
||||
# Database
|
||||
DB_TYPE=
|
||||
DB_USERNAME=
|
||||
DB_PASSWORD=
|
||||
DB_HOST=
|
||||
DB_NAME=
|
||||
DB_FILE=
|
||||
|
||||
# Site
|
||||
LANGUAGE=en-US
|
||||
SITE_NAME=Apache Answer
|
||||
SITE_URL=
|
||||
CONTACT_EMAIL=
|
||||
|
||||
# Admin
|
||||
ADMIN_NAME=
|
||||
ADMIN_PASSWORD=
|
||||
ADMIN_EMAIL=
|
||||
|
||||
# Content
|
||||
EXTERNAL_CONTENT_DISPLAY=ask_before_display
|
||||
|
||||
# Swagger
|
||||
SWAGGER_HOST=
|
||||
SWAGGER_ADDRESS_PORT=
|
||||
|
||||
# Server
|
||||
SITE_ADDR=0.0.0.0:3000
|
||||
|
||||
# Logging
|
||||
LOG_LEVEL=INFO
|
||||
LOG_PATH=
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20.18.1
|
||||
|
||||
@@ -42,15 +42,16 @@ jobs:
|
||||
run: make install-ui-packages ui
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.23
|
||||
|
||||
- name: Install GoReleaser
|
||||
run: go install github.com/goreleaser/goreleaser/v2@latest
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v4
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: latest
|
||||
args: release --clean --skip=validate
|
||||
run: |
|
||||
"$(go env GOPATH)/bin/goreleaser" release --clean --skip=validate
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- uses: actions/upload-artifact@v4
|
||||
|
||||
@@ -35,34 +35,29 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: apache/answer
|
||||
tags: |
|
||||
type=raw,value=latest
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y qemu-user-static
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
run: |
|
||||
docker buildx create --name answer-builder --driver docker-container --use
|
||||
docker buildx inspect --bootstrap
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USER }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
run: echo "${{ secrets.DOCKERHUB_TOKEN }}" | docker login --username "${{ secrets.DOCKERHUB_USER }}" --password-stdin
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
file: ./Dockerfile
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
|
||||
run: |
|
||||
BUILD_DATE="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
||||
docker buildx build \
|
||||
--file ./Dockerfile \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
--push \
|
||||
--tag apache/answer:latest \
|
||||
--label org.opencontainers.image.created="${BUILD_DATE}" \
|
||||
--label org.opencontainers.image.revision="${GITHUB_SHA}" \
|
||||
--label org.opencontainers.image.source="https://github.com/${GITHUB_REPOSITORY}" \
|
||||
--label org.opencontainers.image.version="${GITHUB_REF_NAME#v}" \
|
||||
.
|
||||
|
||||
@@ -33,33 +33,29 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: apache/answer
|
||||
tags: |
|
||||
type=ref,enable=true,priority=600,prefix=,suffix=,event=branch
|
||||
type=semver,pattern={{version}}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y qemu-user-static
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
run: |
|
||||
docker buildx create --name answer-builder --driver docker-container --use
|
||||
docker buildx inspect --bootstrap
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USER }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
run: echo "${{ secrets.DOCKERHUB_TOKEN }}" | docker login --username "${{ secrets.DOCKERHUB_USER }}" --password-stdin
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
file: ./Dockerfile
|
||||
tags: apache/answer:${{ inputs.tag_name }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
run: |
|
||||
BUILD_DATE="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
||||
docker buildx build \
|
||||
--file ./Dockerfile \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
--push \
|
||||
--tag "apache/answer:${{ inputs.tag_name }}" \
|
||||
--label org.opencontainers.image.created="${BUILD_DATE}" \
|
||||
--label org.opencontainers.image.revision="${GITHUB_SHA}" \
|
||||
--label org.opencontainers.image.source="https://github.com/${GITHUB_REPOSITORY}" \
|
||||
--label org.opencontainers.image.version="${{ inputs.tag_name }}" \
|
||||
.
|
||||
|
||||
@@ -34,35 +34,31 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: apache/answer
|
||||
tags: |
|
||||
type=ref,enable=true,priority=600,prefix=,suffix=,event=branch
|
||||
type=semver,pattern={{version}}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y qemu-user-static
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
run: |
|
||||
docker buildx create --name answer-builder --driver docker-container --use
|
||||
docker buildx inspect --bootstrap
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USER }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
run: echo "${{ secrets.DOCKERHUB_TOKEN }}" | docker login --username "${{ secrets.DOCKERHUB_USER }}" --password-stdin
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
file: ./Dockerfile
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
|
||||
env:
|
||||
IMAGE_TAG: ${{ github.ref_name }}
|
||||
run: |
|
||||
BUILD_DATE="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
||||
docker buildx build \
|
||||
--file ./Dockerfile \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
--push \
|
||||
--tag "apache/answer:${IMAGE_TAG#v}" \
|
||||
--label org.opencontainers.image.created="${BUILD_DATE}" \
|
||||
--label org.opencontainers.image.revision="${GITHUB_SHA}" \
|
||||
--label org.opencontainers.image.source="https://github.com/${GITHUB_REPOSITORY}" \
|
||||
--label org.opencontainers.image.version="${IMAGE_TAG#v}" \
|
||||
.
|
||||
|
||||
@@ -30,32 +30,23 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: apache/answer
|
||||
tags: |
|
||||
type=raw,value=test
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
- name: Login to DockerHub
|
||||
run: echo "${{ secrets.DOCKERHUB_TOKEN }}" | docker login --username "${{ secrets.DOCKERHUB_USER }}" --password-stdin
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USER }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
run: |
|
||||
docker buildx create --name answer-builder --driver docker-container --use
|
||||
docker buildx inspect --bootstrap
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
run: |
|
||||
BUILD_DATE="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
||||
docker buildx build \
|
||||
--file ./Dockerfile \
|
||||
--platform linux/amd64 \
|
||||
--push \
|
||||
--tag apache/answer:test \
|
||||
--label org.opencontainers.image.created="${BUILD_DATE}" \
|
||||
--label org.opencontainers.image.revision="${GITHUB_SHA}" \
|
||||
--label org.opencontainers.image.source="https://github.com/${GITHUB_REPOSITORY}" \
|
||||
.
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
# 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.
|
||||
|
||||
name: Lint
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.number || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint (${{ matrix.os }})
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 15
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.23"
|
||||
cache: true
|
||||
|
||||
- name: Run go mod tidy
|
||||
run: go mod tidy
|
||||
|
||||
- name: Run golangci-lint
|
||||
run: make lint
|
||||
|
||||
- name: Check for uncommitted changes
|
||||
shell: bash
|
||||
run: |
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
echo "::error::Uncommitted changes detected"
|
||||
git status
|
||||
git diff
|
||||
exit 1
|
||||
fi
|
||||
+4
-1
@@ -28,8 +28,11 @@ vendor/
|
||||
/answer-data/
|
||||
/answer
|
||||
/new_answer
|
||||
|
||||
build/tools/
|
||||
dist/
|
||||
|
||||
# Lint setup generated file
|
||||
.husky/
|
||||
|
||||
# Environment variables
|
||||
.env
|
||||
@@ -0,0 +1,49 @@
|
||||
# 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.
|
||||
|
||||
version: "2"
|
||||
linters:
|
||||
exclusions:
|
||||
paths:
|
||||
- answer-data
|
||||
- ui
|
||||
- i18n
|
||||
enable:
|
||||
- asasalint # checks for pass []any as any in variadic func(...any)
|
||||
- asciicheck # checks that your code does not contain non-ASCII identifiers
|
||||
- bidichk # checks for dangerous unicode character sequences
|
||||
- bodyclose # checks whether HTTP response body is closed successfully
|
||||
- canonicalheader # checks whether net/http.Header uses canonical header
|
||||
- copyloopvar # detects places where loop variables are copied (Go 1.22+)
|
||||
- gocritic # provides diagnostics that check for bugs, performance and style issues
|
||||
- misspell # finds commonly misspelled English words in comments and strings
|
||||
- modernize # detects code that can be modernized to use newer Go features
|
||||
- testifylint # checks usage of github.com/stretchr/testify
|
||||
- unconvert # removes unnecessary type conversions
|
||||
- unparam # reports unused function parameters
|
||||
- whitespace # detects leading and trailing whitespace
|
||||
|
||||
formatters:
|
||||
enable:
|
||||
- gofmt
|
||||
- goimports
|
||||
settings:
|
||||
gofmt:
|
||||
simplify: true
|
||||
rewrite-rules:
|
||||
- pattern: 'interface{}'
|
||||
replacement: 'any'
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
FROM golang:1.23-alpine AS golang-builder
|
||||
FROM golang:1.25-alpine AS golang-builder
|
||||
LABEL maintainer="linkinstar@apache.org"
|
||||
|
||||
ARG GOPROXY
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.PHONY: build clean ui
|
||||
|
||||
VERSION=1.7.0
|
||||
VERSION=2.0.2
|
||||
BIN=answer
|
||||
DIR_SRC=./cmd/answer
|
||||
DOCKER_CMD=docker
|
||||
@@ -10,6 +10,15 @@ Revision=$(shell git rev-parse --short HEAD 2>/dev/null || echo "")
|
||||
GO_FLAGS=-ldflags="-X github.com/apache/answer/cmd.Version=$(VERSION) -X 'github.com/apache/answer/cmd.Revision=$(Revision)' -X 'github.com/apache/answer/cmd.Time=`date +%s`' -extldflags -static"
|
||||
GO=$(GO_ENV) "$(shell which go)"
|
||||
|
||||
GOLANGCI_VERSION ?= v2.6.2
|
||||
TOOLS_BIN := $(shell mkdir -p build/tools && realpath build/tools)
|
||||
|
||||
GOLANGCI = $(TOOLS_BIN)/golangci-lint-$(GOLANGCI_VERSION)
|
||||
$(GOLANGCI):
|
||||
rm -f $(TOOLS_BIN)/golangci-lint*
|
||||
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/$(GOLANGCI_VERSION)/install.sh | sh -s -- -b $(TOOLS_BIN) $(GOLANGCI_VERSION)
|
||||
mv $(TOOLS_BIN)/golangci-lint $(TOOLS_BIN)/golangci-lint-$(GOLANGCI_VERSION)
|
||||
|
||||
build: generate
|
||||
@$(GO) build $(GO_FLAGS) -o $(BIN) $(DIR_SRC)
|
||||
|
||||
@@ -23,10 +32,10 @@ universal: generate
|
||||
generate:
|
||||
@$(GO) get github.com/swaggo/swag/cmd/swag@v1.16.3
|
||||
@$(GO) get github.com/google/wire/cmd/wire@v0.5.0
|
||||
@$(GO) get go.uber.org/mock/mockgen@v0.5.0
|
||||
@$(GO) get go.uber.org/mock/mockgen@v0.6.0
|
||||
@$(GO) install github.com/swaggo/swag/cmd/swag@v1.16.3
|
||||
@$(GO) install github.com/google/wire/cmd/wire@v0.5.0
|
||||
@$(GO) install go.uber.org/mock/mockgen@v0.5.0
|
||||
@$(GO) install go.uber.org/mock/mockgen@v0.6.0
|
||||
@$(GO) generate ./...
|
||||
@$(GO) mod tidy
|
||||
|
||||
@@ -50,8 +59,12 @@ install-ui-packages:
|
||||
ui:
|
||||
@cd ui && pnpm pre-install && pnpm build && cd -
|
||||
|
||||
lint: generate
|
||||
lint: generate $(GOLANGCI)
|
||||
@bash ./script/check-asf-header.sh
|
||||
@gofmt -w -l .
|
||||
$(GOLANGCI) run
|
||||
|
||||
lint-fix: generate $(GOLANGCI)
|
||||
@bash ./script/check-asf-header.sh
|
||||
$(GOLANGCI) run --fix
|
||||
|
||||
all: clean build
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Apache Answer
|
||||
Copyright 2023-2025 The Apache Software Foundation
|
||||
Copyright 2023-2026 The Apache Software Foundation
|
||||
|
||||
This product includes software developed at
|
||||
The Apache Software Foundation (https://www.apache.org/).
|
||||
|
||||
@@ -23,7 +23,7 @@ To learn more about the project, visit [answer.apache.org](https://answer.apache
|
||||
### Running with docker
|
||||
|
||||
```bash
|
||||
docker run -d -p 9080:80 -v answer-data:/data --name answer apache/answer:1.7.0
|
||||
docker run -d -p 9080:80 -v answer-data:/data --name answer apache/answer:2.0.2
|
||||
```
|
||||
|
||||
For more information, see [Installation](https://answer.apache.org/docs/installation).
|
||||
@@ -43,7 +43,7 @@ You can also check out the [plugins here](https://answer.apache.org/plugins).
|
||||
- Golang >= 1.23
|
||||
- Node.js >= 20
|
||||
- pnpm >= 9
|
||||
- [mockgen](https://github.com/uber-go/mock?tab=readme-ov-file#installation) >= 1.6.0
|
||||
- [mockgen](https://github.com/uber-go/mock?tab=readme-ov-file#installation) >= 0.6.0
|
||||
- [wire](https://github.com/google/wire/) >= 0.5.0
|
||||
|
||||
### Build
|
||||
|
||||
@@ -64,8 +64,23 @@ spec:
|
||||
port: http
|
||||
resources:
|
||||
{{- toYaml .Values.resources | nindent 12 }}
|
||||
{{- if .Values.env }}
|
||||
{{- $envNames := list }}
|
||||
{{- range .Values.env }}
|
||||
{{- $envNames = append $envNames .name }}
|
||||
{{- end }}
|
||||
env:
|
||||
{{- if not (has "INSTALL_PORT" $envNames) }}
|
||||
- name: INSTALL_PORT
|
||||
value: {{ .Values.service.port | quote }}
|
||||
{{- end }}
|
||||
{{- if not (has "SITE_ADDR" $envNames) }}
|
||||
- name: SITE_ADDR
|
||||
value: {{ printf "0.0.0.0:%v" .Values.service.port | quote }}
|
||||
{{- end }}
|
||||
{{- if not (has "SWAGGER_ADDRESS_PORT" $envNames) }}
|
||||
- name: SWAGGER_ADDRESS_PORT
|
||||
value: {{ printf ":%v" .Values.service.port | quote }}
|
||||
{{- end }}
|
||||
{{- range .Values.env }}
|
||||
- name: {{ .name }}
|
||||
{{- if .value | quote }}
|
||||
@@ -76,7 +91,6 @@ spec:
|
||||
{{- toYaml .valueFrom | nindent 16 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: "/data"
|
||||
@@ -102,4 +116,4 @@ spec:
|
||||
{{- with .Values.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
@@ -31,12 +31,18 @@ import (
|
||||
"github.com/apache/answer/internal/base/path"
|
||||
"github.com/apache/answer/internal/schema"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/segmentfault/pacman"
|
||||
"github.com/segmentfault/pacman/contrib/log/zap"
|
||||
"github.com/segmentfault/pacman/contrib/server/http"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Load .env if present, ignore error to keep backward compatibility
|
||||
_ = godotenv.Load()
|
||||
}
|
||||
|
||||
// go build -ldflags "-X github.com/apache/answer/cmd.Version=x.y.z"
|
||||
var (
|
||||
// Name is the name of the project
|
||||
|
||||
+1
-2
@@ -1,5 +1,4 @@
|
||||
//go:build wireinject
|
||||
// +build wireinject
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
@@ -32,7 +31,7 @@ import (
|
||||
"github.com/apache/answer/internal/base/server"
|
||||
"github.com/apache/answer/internal/base/translator"
|
||||
"github.com/apache/answer/internal/controller"
|
||||
"github.com/apache/answer/internal/controller/template_render"
|
||||
templaterender "github.com/apache/answer/internal/controller/template_render"
|
||||
"github.com/apache/answer/internal/controller_admin"
|
||||
"github.com/apache/answer/internal/repo"
|
||||
"github.com/apache/answer/internal/router"
|
||||
|
||||
+52
-33
@@ -38,7 +38,9 @@ import (
|
||||
"github.com/apache/answer/internal/controller_admin"
|
||||
"github.com/apache/answer/internal/repo/activity"
|
||||
"github.com/apache/answer/internal/repo/activity_common"
|
||||
"github.com/apache/answer/internal/repo/ai_conversation"
|
||||
"github.com/apache/answer/internal/repo/answer"
|
||||
"github.com/apache/answer/internal/repo/api_key"
|
||||
"github.com/apache/answer/internal/repo/auth"
|
||||
"github.com/apache/answer/internal/repo/badge"
|
||||
"github.com/apache/answer/internal/repo/badge_award"
|
||||
@@ -72,8 +74,10 @@ import (
|
||||
"github.com/apache/answer/internal/service/action"
|
||||
activity2 "github.com/apache/answer/internal/service/activity"
|
||||
activity_common2 "github.com/apache/answer/internal/service/activity_common"
|
||||
"github.com/apache/answer/internal/service/activity_queue"
|
||||
"github.com/apache/answer/internal/service/activityqueue"
|
||||
ai_conversation2 "github.com/apache/answer/internal/service/ai_conversation"
|
||||
"github.com/apache/answer/internal/service/answer_common"
|
||||
"github.com/apache/answer/internal/service/apikey"
|
||||
auth2 "github.com/apache/answer/internal/service/auth"
|
||||
badge2 "github.com/apache/answer/internal/service/badge"
|
||||
collection2 "github.com/apache/answer/internal/service/collection"
|
||||
@@ -83,14 +87,16 @@ import (
|
||||
config2 "github.com/apache/answer/internal/service/config"
|
||||
"github.com/apache/answer/internal/service/content"
|
||||
"github.com/apache/answer/internal/service/dashboard"
|
||||
"github.com/apache/answer/internal/service/event_queue"
|
||||
"github.com/apache/answer/internal/service/embedding"
|
||||
"github.com/apache/answer/internal/service/eventqueue"
|
||||
export2 "github.com/apache/answer/internal/service/export"
|
||||
"github.com/apache/answer/internal/service/feature_toggle"
|
||||
file_record2 "github.com/apache/answer/internal/service/file_record"
|
||||
"github.com/apache/answer/internal/service/follow"
|
||||
"github.com/apache/answer/internal/service/importer"
|
||||
meta2 "github.com/apache/answer/internal/service/meta"
|
||||
"github.com/apache/answer/internal/service/meta_common"
|
||||
"github.com/apache/answer/internal/service/notice_queue"
|
||||
"github.com/apache/answer/internal/service/noticequeue"
|
||||
"github.com/apache/answer/internal/service/notification"
|
||||
"github.com/apache/answer/internal/service/notification_common"
|
||||
"github.com/apache/answer/internal/service/object_info"
|
||||
@@ -114,6 +120,7 @@ import (
|
||||
"github.com/apache/answer/internal/service/user_common"
|
||||
user_external_login2 "github.com/apache/answer/internal/service/user_external_login"
|
||||
user_notification_config2 "github.com/apache/answer/internal/service/user_notification_config"
|
||||
"github.com/apache/answer/internal/service/vector_sync"
|
||||
"github.com/segmentfault/pacman"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
)
|
||||
@@ -144,7 +151,8 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
|
||||
siteInfoCommonService := siteinfo_common.NewSiteInfoCommonService(siteInfoRepo)
|
||||
langController := controller.NewLangController(i18nTranslator, siteInfoCommonService)
|
||||
authRepo := auth.NewAuthRepo(dataData)
|
||||
authService := auth2.NewAuthService(authRepo)
|
||||
apiKeyRepo := api_key.NewAPIKeyRepo(dataData)
|
||||
authService := auth2.NewAuthService(authRepo, apiKeyRepo)
|
||||
userRepo := user.NewUserRepo(dataData)
|
||||
uniqueIDRepo := unique.NewUniqueIDRepo(dataData)
|
||||
configRepo := config.NewConfigRepo(dataData)
|
||||
@@ -172,47 +180,48 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
|
||||
tagRepo := tag.NewTagRepo(dataData, uniqueIDRepo)
|
||||
revisionRepo := revision.NewRevisionRepo(dataData, uniqueIDRepo)
|
||||
revisionService := revision_common.NewRevisionService(revisionRepo, userRepo)
|
||||
activityQueueService := activity_queue.NewActivityQueueService()
|
||||
tagCommonService := tag_common2.NewTagCommonService(tagCommonRepo, tagRelRepo, tagRepo, revisionService, siteInfoCommonService, activityQueueService)
|
||||
service := activityqueue.NewService()
|
||||
tagCommonService := tag_common2.NewTagCommonService(tagCommonRepo, tagRelRepo, tagRepo, revisionService, siteInfoCommonService, service)
|
||||
collectionRepo := collection.NewCollectionRepo(dataData, uniqueIDRepo)
|
||||
collectionCommon := collectioncommon.NewCollectionCommon(collectionRepo)
|
||||
answerCommon := answercommon.NewAnswerCommon(answerRepo)
|
||||
metaRepo := meta.NewMetaRepo(dataData)
|
||||
metaCommonService := metacommon.NewMetaCommonService(metaRepo)
|
||||
questionCommon := questioncommon.NewQuestionCommon(questionRepo, answerRepo, voteRepo, followRepo, tagCommonService, userCommon, collectionCommon, answerCommon, metaCommonService, configService, activityQueueService, revisionRepo, siteInfoCommonService, dataData)
|
||||
eventQueueService := event_queue.NewEventQueueService()
|
||||
questionCommon := questioncommon.NewQuestionCommon(questionRepo, answerRepo, voteRepo, followRepo, tagCommonService, userCommon, collectionCommon, answerCommon, metaCommonService, configService, service, revisionRepo, siteInfoCommonService, dataData)
|
||||
eventqueueService := eventqueue.NewService()
|
||||
fileRecordRepo := file_record.NewFileRecordRepo(dataData)
|
||||
fileRecordService := file_record2.NewFileRecordService(fileRecordRepo, revisionRepo, serviceConf, siteInfoCommonService, userCommon)
|
||||
userService := content.NewUserService(userRepo, userActiveActivityRepo, activityRepo, emailService, authService, siteInfoCommonService, userRoleRelService, userCommon, userExternalLoginService, userNotificationConfigRepo, userNotificationConfigService, questionCommon, eventQueueService, fileRecordService)
|
||||
userService := content.NewUserService(userRepo, userActiveActivityRepo, activityRepo, emailService, authService, siteInfoCommonService, userRoleRelService, userCommon, userExternalLoginService, userNotificationConfigRepo, userNotificationConfigService, questionCommon, eventqueueService, fileRecordService)
|
||||
captchaRepo := captcha.NewCaptchaRepo(dataData)
|
||||
captchaService := action.NewCaptchaService(captchaRepo)
|
||||
userController := controller.NewUserController(authService, userService, captchaService, emailService, siteInfoCommonService, userNotificationConfigService)
|
||||
commentRepo := comment.NewCommentRepo(dataData, uniqueIDRepo)
|
||||
commentCommonRepo := comment.NewCommentCommonRepo(dataData, uniqueIDRepo)
|
||||
objService := object_info.NewObjService(answerRepo, questionRepo, commentCommonRepo, tagCommonRepo, tagCommonService)
|
||||
notificationQueueService := notice_queue.NewNotificationQueueService()
|
||||
externalNotificationQueueService := notice_queue.NewNewQuestionNotificationQueueService()
|
||||
noticequeueService := noticequeue.NewService()
|
||||
externalService := noticequeue.NewExternalService()
|
||||
reviewRepo := review.NewReviewRepo(dataData)
|
||||
vector_syncService := vector_sync.NewService(dataData)
|
||||
reviewService := review2.NewReviewService(reviewRepo, objService, userCommon, userRepo, questionRepo, answerRepo, userRoleRelService, externalService, tagCommonService, questionCommon, noticequeueService, siteInfoCommonService, commentCommonRepo, vector_syncService)
|
||||
commentService := comment2.NewCommentService(commentRepo, commentCommonRepo, userCommon, objService, voteRepo, emailService, userRepo, noticequeueService, externalService, service, eventqueueService, reviewService, vector_syncService)
|
||||
rolePowerRelRepo := role.NewRolePowerRelRepo(dataData)
|
||||
rolePowerRelService := role2.NewRolePowerRelService(rolePowerRelRepo, userRoleRelService)
|
||||
rankService := rank2.NewRankService(userCommon, userRankRepo, objService, userRoleRelService, rolePowerRelService, configService)
|
||||
limitRepo := limit.NewRateLimitRepo(dataData)
|
||||
rateLimitMiddleware := middleware.NewRateLimitMiddleware(limitRepo)
|
||||
reportRepo := report.NewReportRepo(dataData, uniqueIDRepo)
|
||||
tagService := tag2.NewTagService(tagRepo, tagCommonService, revisionService, followRepo, siteInfoCommonService, activityQueueService)
|
||||
answerActivityRepo := activity.NewAnswerActivityRepo(dataData, activityRepo, userRankRepo, notificationQueueService)
|
||||
answerActivityService := activity2.NewAnswerActivityService(answerActivityRepo, configService)
|
||||
externalNotificationService := notification.NewExternalNotificationService(dataData, userNotificationConfigRepo, followRepo, emailService, userRepo, externalNotificationQueueService, userExternalLoginRepo, siteInfoCommonService)
|
||||
reviewRepo := review.NewReviewRepo(dataData)
|
||||
reviewService := review2.NewReviewService(reviewRepo, objService, userCommon, userRepo, questionRepo, answerRepo, userRoleRelService, externalNotificationQueueService, tagCommonService, questionCommon, notificationQueueService, siteInfoCommonService, commentCommonRepo)
|
||||
questionService := content.NewQuestionService(activityRepo, questionRepo, answerRepo, tagCommonService, tagService, questionCommon, userCommon, userRepo, userRoleRelService, revisionService, metaCommonService, collectionCommon, answerActivityService, emailService, notificationQueueService, externalNotificationQueueService, activityQueueService, siteInfoCommonService, externalNotificationService, reviewService, configService, eventQueueService, reviewRepo)
|
||||
answerService := content.NewAnswerService(answerRepo, questionRepo, questionCommon, userCommon, collectionCommon, userRepo, revisionService, answerActivityService, answerCommon, voteRepo, emailService, userRoleRelService, notificationQueueService, externalNotificationQueueService, activityQueueService, reviewService, eventQueueService)
|
||||
commentService := comment2.NewCommentService(commentRepo, commentCommonRepo, userCommon, objService, voteRepo, emailService, userRepo, notificationQueueService, externalNotificationQueueService, activityQueueService, eventQueueService, reviewService)
|
||||
commentController := controller.NewCommentController(commentService, rankService, captchaService, rateLimitMiddleware)
|
||||
reportRepo := report.NewReportRepo(dataData, uniqueIDRepo)
|
||||
tagService := tag2.NewTagService(tagRepo, tagCommonService, revisionService, followRepo, siteInfoCommonService, service)
|
||||
answerActivityRepo := activity.NewAnswerActivityRepo(dataData, activityRepo, userRankRepo, noticequeueService)
|
||||
answerActivityService := activity2.NewAnswerActivityService(answerActivityRepo, configService)
|
||||
externalNotificationService := notification.NewExternalNotificationService(dataData, userNotificationConfigRepo, followRepo, emailService, userRepo, externalService, userExternalLoginRepo, siteInfoCommonService)
|
||||
questionService := content.NewQuestionService(activityRepo, questionRepo, answerRepo, tagCommonService, tagService, questionCommon, userCommon, userRepo, userRoleRelService, revisionService, metaCommonService, collectionCommon, answerActivityService, emailService, noticequeueService, externalService, service, siteInfoCommonService, externalNotificationService, reviewService, configService, eventqueueService, reviewRepo, vector_syncService)
|
||||
answerService := content.NewAnswerService(answerRepo, questionRepo, questionCommon, userCommon, collectionCommon, userRepo, revisionService, answerActivityService, answerCommon, voteRepo, emailService, userRoleRelService, noticequeueService, externalService, service, reviewService, eventqueueService, vector_syncService)
|
||||
reportHandle := report_handle.NewReportHandle(questionService, answerService, commentService)
|
||||
reportService := report2.NewReportService(reportRepo, objService, userCommon, answerRepo, questionRepo, commentCommonRepo, reportHandle, configService, eventQueueService)
|
||||
reportService := report2.NewReportService(reportRepo, objService, userCommon, answerRepo, questionRepo, commentCommonRepo, reportHandle, configService, eventqueueService)
|
||||
reportController := controller.NewReportController(reportService, rankService, captchaService)
|
||||
contentVoteRepo := activity.NewVoteRepo(dataData, activityRepo, userRankRepo, notificationQueueService)
|
||||
voteService := content.NewVoteService(contentVoteRepo, configService, questionRepo, answerRepo, commentCommonRepo, objService, eventQueueService)
|
||||
contentVoteRepo := activity.NewVoteRepo(dataData, activityRepo, userRankRepo, noticequeueService)
|
||||
voteService := content.NewVoteService(contentVoteRepo, configService, questionRepo, answerRepo, commentCommonRepo, objService, eventqueueService)
|
||||
voteController := controller.NewVoteController(voteService, rankService, captchaService)
|
||||
tagController := controller.NewTagController(tagService, tagCommonService, rankService)
|
||||
followFollowRepo := activity.NewFollowRepo(dataData, uniqueIDRepo, activityRepo)
|
||||
@@ -228,14 +237,14 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
|
||||
searchService := content.NewSearchService(searchParser, searchRepo)
|
||||
searchController := controller.NewSearchController(searchService, captchaService)
|
||||
reviewActivityRepo := activity.NewReviewActivityRepo(dataData, activityRepo, userRankRepo, configService)
|
||||
contentRevisionService := content.NewRevisionService(revisionRepo, userCommon, questionCommon, answerService, objService, questionRepo, answerRepo, tagRepo, tagCommonService, notificationQueueService, activityQueueService, reportRepo, reviewService, reviewActivityRepo)
|
||||
contentRevisionService := content.NewRevisionService(revisionRepo, userCommon, questionCommon, answerService, objService, questionRepo, answerRepo, tagRepo, tagCommonService, noticequeueService, service, reportRepo, reviewService, reviewActivityRepo)
|
||||
revisionController := controller.NewRevisionController(contentRevisionService, rankService)
|
||||
rankController := controller.NewRankController(rankService)
|
||||
userAdminRepo := user.NewUserAdminRepo(dataData, authRepo)
|
||||
notificationRepo := notification2.NewNotificationRepo(dataData)
|
||||
pluginUserConfigRepo := plugin_config.NewPluginUserConfigRepo(dataData)
|
||||
badgeAwardRepo := badge_award.NewBadgeAwardRepo(dataData, uniqueIDRepo)
|
||||
userAdminService := user_admin.NewUserAdminService(userAdminRepo, userRoleRelService, authService, userCommon, userActiveActivityRepo, siteInfoCommonService, emailService, questionRepo, answerRepo, commentCommonRepo, userExternalLoginRepo, notificationRepo, pluginUserConfigRepo, badgeAwardRepo)
|
||||
userAdminService := user_admin.NewUserAdminService(userAdminRepo, userRoleRelService, authService, userCommon, userActiveActivityRepo, siteInfoCommonService, emailService, questionRepo, answerRepo, commentCommonRepo, userExternalLoginRepo, notificationRepo, pluginUserConfigRepo, badgeAwardRepo, apiKeyRepo)
|
||||
userAdminController := controller_admin.NewUserAdminController(userAdminService)
|
||||
reasonRepo := reason.NewReasonRepo(configService)
|
||||
reasonService := reason2.NewReasonService(reasonRepo)
|
||||
@@ -244,7 +253,7 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
|
||||
siteInfoService := siteinfo.NewSiteInfoService(siteInfoRepo, siteInfoCommonService, emailService, tagCommonService, configService, questionCommon, fileRecordService)
|
||||
siteInfoController := controller_admin.NewSiteInfoController(siteInfoService)
|
||||
controllerSiteInfoController := controller.NewSiteInfoController(siteInfoCommonService)
|
||||
notificationCommon := notificationcommon.NewNotificationCommon(dataData, notificationRepo, userCommon, activityRepo, followRepo, objService, notificationQueueService, userExternalLoginRepo, siteInfoCommonService)
|
||||
notificationCommon := notificationcommon.NewNotificationCommon(dataData, notificationRepo, userCommon, activityRepo, followRepo, objService, noticequeueService, userExternalLoginRepo, siteInfoCommonService)
|
||||
badgeRepo := badge.NewBadgeRepo(dataData, uniqueIDRepo)
|
||||
notificationService := notification.NewNotificationService(dataData, notificationRepo, notificationCommon, revisionService, userRepo, reportRepo, reviewService, badgeRepo)
|
||||
notificationController := controller.NewNotificationController(notificationService, rankService)
|
||||
@@ -253,7 +262,7 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
|
||||
uploaderService := uploader.NewUploaderService(serviceConf, siteInfoCommonService, fileRecordService)
|
||||
uploadController := controller.NewUploadController(uploaderService)
|
||||
activityActivityRepo := activity.NewActivityRepo(dataData, configService)
|
||||
activityCommon := activity_common2.NewActivityCommon(activityRepo, activityQueueService)
|
||||
activityCommon := activity_common2.NewActivityCommon(activityRepo, service)
|
||||
commentCommonService := comment_common.NewCommentCommonService(commentCommonRepo)
|
||||
activityService := activity2.NewActivityService(activityActivityRepo, userCommon, activityCommon, tagCommonService, objService, commentCommonService, revisionService, metaCommonService, configService)
|
||||
activityController := controller.NewActivityController(activityService)
|
||||
@@ -265,23 +274,33 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
|
||||
permissionController := controller.NewPermissionController(rankService)
|
||||
userPluginController := controller.NewUserPluginController(pluginCommonService)
|
||||
reviewController := controller.NewReviewController(reviewService, rankService, captchaService)
|
||||
metaService := meta2.NewMetaService(metaCommonService, userCommon, answerRepo, questionRepo, eventQueueService)
|
||||
metaService := meta2.NewMetaService(metaCommonService, userCommon, answerRepo, questionRepo, eventqueueService)
|
||||
metaController := controller.NewMetaController(metaService)
|
||||
badgeGroupRepo := badge_group.NewBadgeGroupRepo(dataData, uniqueIDRepo)
|
||||
eventRuleRepo := badge.NewEventRuleRepo(dataData)
|
||||
badgeAwardService := badge2.NewBadgeAwardService(badgeAwardRepo, badgeRepo, userCommon, objService, notificationQueueService)
|
||||
badgeEventService := badge2.NewBadgeEventService(dataData, eventQueueService, badgeRepo, eventRuleRepo, badgeAwardService)
|
||||
badgeAwardService := badge2.NewBadgeAwardService(badgeAwardRepo, badgeRepo, userCommon, objService, noticequeueService)
|
||||
badgeEventService := badge2.NewBadgeEventService(dataData, eventqueueService, badgeRepo, eventRuleRepo, badgeAwardService)
|
||||
badgeService := badge2.NewBadgeService(badgeRepo, badgeGroupRepo, badgeAwardRepo, badgeEventService, siteInfoCommonService)
|
||||
badgeController := controller.NewBadgeController(badgeService, badgeAwardService)
|
||||
controller_adminBadgeController := controller_admin.NewBadgeController(badgeService)
|
||||
answerAPIRouter := router.NewAnswerAPIRouter(langController, userController, commentController, reportController, voteController, tagController, followController, collectionController, questionController, answerController, searchController, revisionController, rankController, userAdminController, reasonController, themeController, siteInfoController, controllerSiteInfoController, notificationController, dashboardController, uploadController, activityController, roleController, pluginController, permissionController, userPluginController, reviewController, metaController, badgeController, controller_adminBadgeController)
|
||||
apiKeyService := apikey.NewAPIKeyService(apiKeyRepo)
|
||||
adminAPIKeyController := controller_admin.NewAdminAPIKeyController(apiKeyService)
|
||||
featureToggleService := feature_toggle.NewFeatureToggleService(siteInfoRepo)
|
||||
embeddingService := embedding.NewEmbeddingService()
|
||||
mcpController := controller.NewMCPController(searchService, siteInfoCommonService, tagCommonService, questionCommon, commentRepo, userCommon, answerRepo, featureToggleService, embeddingService)
|
||||
aiConversationRepo := ai_conversation.NewAIConversationRepo(dataData)
|
||||
aiConversationService := ai_conversation2.NewAIConversationService(aiConversationRepo, userCommon)
|
||||
aiController := controller.NewAIController(searchService, siteInfoCommonService, tagCommonService, questionCommon, commentRepo, userCommon, answerRepo, mcpController, aiConversationService, featureToggleService)
|
||||
aiConversationController := controller.NewAIConversationController(aiConversationService, featureToggleService)
|
||||
aiConversationAdminController := controller_admin.NewAIConversationAdminController(aiConversationService, featureToggleService)
|
||||
answerAPIRouter := router.NewAnswerAPIRouter(langController, userController, commentController, reportController, voteController, tagController, followController, collectionController, questionController, answerController, searchController, revisionController, rankController, userAdminController, reasonController, themeController, siteInfoController, controllerSiteInfoController, notificationController, dashboardController, uploadController, activityController, roleController, pluginController, permissionController, userPluginController, reviewController, metaController, badgeController, controller_adminBadgeController, adminAPIKeyController, aiController, aiConversationController, aiConversationAdminController, mcpController)
|
||||
swaggerRouter := router.NewSwaggerRouter(swaggerConf)
|
||||
uiRouter := router.NewUIRouter(controllerSiteInfoController, siteInfoCommonService)
|
||||
authUserMiddleware := middleware.NewAuthUserMiddleware(authService, siteInfoCommonService)
|
||||
avatarMiddleware := middleware.NewAvatarMiddleware(serviceConf, uploaderService)
|
||||
shortIDMiddleware := middleware.NewShortIDMiddleware(siteInfoCommonService)
|
||||
templateRenderController := templaterender.NewTemplateRenderController(questionService, userService, tagService, answerService, commentService, siteInfoCommonService, questionRepo)
|
||||
templateController := controller.NewTemplateController(templateRenderController, siteInfoCommonService, eventQueueService, userService, questionService)
|
||||
templateController := controller.NewTemplateController(templateRenderController, siteInfoCommonService, eventqueueService, userService, questionService)
|
||||
templateRouter := router.NewTemplateRouter(templateController, templateRenderController, siteInfoController, authUserMiddleware)
|
||||
connectorController := controller.NewConnectorController(siteInfoCommonService, emailService, userExternalLoginService)
|
||||
userCenterLoginService := user_external_login2.NewUserCenterLoginService(userRepo, userCommon, userExternalLoginRepo, userActiveActivityRepo, siteInfoCommonService)
|
||||
|
||||
+1659
-256
File diff suppressed because it is too large
Load Diff
+38
-32
@@ -214,11 +214,12 @@ Apache 2.0 licenses
|
||||
|
||||
The following components are provided under the Apache 2.0 License.
|
||||
|
||||
(Apache License, Version 2.0) react-helmet-async (https://github.com/staylor/react-helmet-async) [link](./licenses/LICENSE-staylor-react-helmet-async.txt)
|
||||
(Apache License, Version 2.0) golang-mock (https://github.com/golang/mock) [link](./licenses/LICENSE-golang-mock.txt)
|
||||
(Apache License, Version 2.0) gomock (https://github.com/uber-go/mock) [link](./licenses/LICENSE-uber-go-mock.txt)
|
||||
(Apache License, Version 2.0) google-wire (https://github.com/google/wire) [link](./licenses/LICENSE-google-wire.txt)
|
||||
(Apache License, Version 2.0) mojocn-base64Captcha (https://github.com/mojocn/base64Captcha) [link](./licenses/LICENSE-mojocn-base64Captcha.txt)
|
||||
(Apache License, Version 2.0) ory-dockertest (https://github.com/ory/dockertest) [link](./licenses/LICENSE-ory-dockertest.txt)
|
||||
(Apache License, Version 2.0) react-helmet-async (https://github.com/staylor/react-helmet-async) [link](./licenses/LICENSE-staylor-react-helmet-async.txt)
|
||||
(Apache License, Version 2.0) sashabaranov-go-openai (https://github.com/sashabaranov/go-openai) [link](./licenses/LICENSE-sashabaranov-go-openai.txt)
|
||||
(Apache License, Version 2.0) spf13-cobra (https://github.com/spf13/cobra) [link](./licenses/LICENSE-spf13-cobra.txt)
|
||||
|
||||
========================================================================
|
||||
@@ -227,57 +228,62 @@ MIT licenses
|
||||
|
||||
The following components are provided under the MIT License. See project link for details.
|
||||
|
||||
(MIT License) axios (https://github.com/axios/axios) [link](./licenses/LICENSE-axios-axios.txt)
|
||||
(MIT License) bootstrap (https://github.com/twbs/bootstrap) [link](./licenses/LICENSE-twbs-bootstrap.txt)
|
||||
(MIT License) icons (https://github.com/twbs/icons) [link](./licenses/LICENSE-twbs-icons.txt)
|
||||
(MIT License) classnames (https://github.com/JedWatson/classnames) [link](./LICENSE-JedWatson-classnames.txt)
|
||||
(MIT License) codemirror (https://github.com/codemirror/basic-setup) [link](./licenses/LICENSE-codemirror-basic-setup.txt)
|
||||
(MIT License) @codemirror/lang-markdown (https://github.com/codemirror/lang-markdown) [link](./licenses/LICENSE-codemirror-lang-markdown.txt)
|
||||
(MIT License) @codemirror/language-data (https://github.com/codemirror/language-data) [link](./licenses/LICENSE-codemirror-language-data.txt)
|
||||
(MIT License) @codemirror/state (https://github.com/codemirror/state) [link](./licenses/LICENSE-codemirror-state.txt)
|
||||
(MIT License) @codemirror/view (https://github.com/codemirror/view) [link](./licenses/LICENSE-codemirror-view.txt)
|
||||
(MIT License) anargu-gin-brotli (https://github.com/anargu/gin-brotli) [link](./licenses/LICENSE-anargu-gin-brotli.txt)
|
||||
(MIT License) asaskevich-govalidator (https://github.com/asaskevich/govalidator) [link](./licenses/LICENSE-asaskevich-govalidator.txt)
|
||||
(MIT License) axios (https://github.com/axios/axios) [link](./licenses/LICENSE-axios-axios.txt)
|
||||
(MIT License) bootstrap (https://github.com/twbs/bootstrap) [link](./licenses/LICENSE-twbs-bootstrap.txt)
|
||||
(MIT License) classnames (https://github.com/JedWatson/classnames) [link](./LICENSE-JedWatson-classnames.txt)
|
||||
(MIT License) codemirror (https://github.com/codemirror/basic-setup) [link](./licenses/LICENSE-codemirror-basic-setup.txt)
|
||||
(MIT License) color (https://github.com/Qix-/color) [link](./licenses/LICENSE-Qix--color.txt)
|
||||
(MIT License) copy-to-clipboard (https://github.com/sudodoki/copy-to-clipboard) [link](./licenses/LICENSE-sudodoki-copy-to-clipboard.txt)
|
||||
(MIT License) dayjs (https://github.com/iamkun/dayjs) [link](./licenses/LICENSE-iamkun-dayjs.txt)
|
||||
(MIT License) disintegration-imaging (https://github.com/disintegration/imaging) [link](./licenses/LICENSE-disintegration-imaging.txt)
|
||||
(MIT License) front-matter (https://github.com/jxson/front-matter) [link](./licenses/LICENSE-jxson-front-matter.txt)
|
||||
(MIT License) gin-gonic-gin (https://github.com/gin-gonic/gin) [link](./licenses/LICENSE-gin-gonic-gin.txt)
|
||||
(MIT License) go-gomail-gomail (https://gopkg.in/gomail.v2) [link](./licenses/LICENSE-go-gomail-gomail.txt)
|
||||
(MIT License) go-playground-locales (https://github.com/go-playground/locales) [link](./licenses/LICENSE-go-playground-locales.txt)
|
||||
(MIT License) go-playground-universal-translator (https://github.com/go-playground/universal-translator) [link](./licenses/LICENSE-go-playground-universal-translator.txt)
|
||||
(MIT License) go-playground-validator (https://github.com/go-playground/validator) [link](./licenses/LICENSE-go-playground-validator.txt)
|
||||
(MIT License) go-resty-resty (https://github.com/go-resty/resty) [link](./licenses/LICENSE-go-resty-resty.txt)
|
||||
(MIT License) goccy-go-json (https://github.com/goccy/go-json) [link](./licenses/LICENSE-goccy-go-json.txt)
|
||||
(MIT License) i18next (https://github.com/i18next/i18next) [link](./licenses/LICENSE-i18next-i18next.txt)
|
||||
(MIT License) icons (https://github.com/twbs/icons) [link](./licenses/LICENSE-twbs-icons.txt)
|
||||
(MIT License) jinzhu-copier (https://github.com/jinzhu/copier) [link](./licenses/LICENSE-jinzhu-copier.txt)
|
||||
(MIT License) jinzhu-now (https://github.com/jinzhu/now) [link](./licenses/LICENSE-jinzhu-now.txt)
|
||||
(MIT License) joho-godotenv (https://github.com/joho/godotenv) [link](./licenses/LICENSE-joho-godotenv.txt)
|
||||
(MIT License) jordan-wright-email (https://github.com/jordan-wright/email) [link](./licenses/LICENSE-jordan-wright-email.txt)
|
||||
(MIT License) js-sha256 (https://github.com/emn178/js-sha256) [link](./licenses/LICENSE-emn178-js-sha256.txt)
|
||||
(MIT License) lib-pq (https://github.com/lib/pq) [link](./licenses/LICENSE-lib-pq.txt)
|
||||
(MIT License) lodash (https://github.com/lodash/lodash) [link](./licenses/LICENSE-lodash-lodash.txt)
|
||||
(MIT License) Machiel-slugify (https://github.com/Machiel/slugify) [link](./licenses/LICENSE-Machiel-slugify.txt)
|
||||
(MIT License) mark3labs-mcp-go (https://github.com/mark3labs/mcp-go) [link](./licenses/LICENSE-mark3labs-mcp-go.txt)
|
||||
(MIT License) marked (https://github.com/markedjs/marked) [link](./licenses/LICENSE-markedjs-marked.txt)
|
||||
(MIT License) Masterminds-semver (https://github.com/Masterminds/semver) [link](./licenses/LICENSE-Masterminds-semver.txt)
|
||||
(MIT License) mattn-go-sqlite3 (https://github.com/mattn/go-sqlite3) [link](./licenses/LICENSE-mattn-go-sqlite3.txt)
|
||||
(MIT License) mozillazg-go-pinyin (https://github.com/mozillazg/go-pinyin) [link](./licenses/LICENSE-mozillazg-go-pinyin.txt)
|
||||
(MIT License) mozillazg-go-unidecode (https://github.com/mozillazg/go-unidecode) [link](./licenses/LICENSE-mozillazg-go-unidecode.txt)
|
||||
(MIT License) next-share (https://github.com/Bunlong/next-share) [link](./licenses/LIcENSE-Bunlong-next-share.txt)
|
||||
(MIT License) node-qrcode (https://github.com/soldair/node-qrcode) [link](./licenses/LICENSE-soldair-qrcode.txt)
|
||||
(MIT License) react (https://github.com/facebook/react) [link](./licenses/LICENSE-facebook-react.txt)
|
||||
(MIT License) react-bootstrap (https://github.com/react-bootstrap/react-bootstrap) [link](./licenses/LICENSE-react-bootstrap-react-bootstrap.txt)
|
||||
(MIT License) react-i18next (https://github.com/i18next/react-i18next) [link](./licenses/LICENSE-i18next-react-i18next.txt)
|
||||
(MIT License) react-router (https://github.com/remix-run/react-router) [link](./licenses/LICENSE-remix-run-react-router.txt)
|
||||
(MIT License) swr (https://github.com/vercel/swr) [link](./licenses/LICENSE-vercel-swr.txt)
|
||||
(MIT License) zustand (https://github.com/pmndrs/zustand) [link](./licenses/LICENSE-pmndrs-zustand.txt)
|
||||
(MIT License) mozillazg-go-pinyin (https://github.com/mozillazg/go-pinyin) [link](./licenses/LICENSE-mozillazg-go-pinyin.txt)
|
||||
(MIT License) Machiel-slugify (https://github.com/Machiel/slugify) [link](./licenses/LICENSE-Machiel-slugify.txt)
|
||||
(MIT License) Masterminds-semver (https://github.com/Masterminds/semver) [link](./licenses/LICENSE-Masterminds-semver.txt)
|
||||
(MIT License) anargu-gin-brotli (https://github.com/anargu/gin-brotli) [link](./licenses/LICENSE-anargu-gin-brotli.txt)
|
||||
(MIT License) asaskevich-govalidator (https://github.com/asaskevich/govalidator) [link](./licenses/LICENSE-asaskevich-govalidator.txt)
|
||||
(MIT License) disintegration-imaging (https://github.com/disintegration/imaging) [link](./licenses/LICENSE-disintegration-imaging.txt)
|
||||
(MIT License) gin-gonic-gin (https://github.com/gin-gonic/gin) [link](./licenses/LICENSE-gin-gonic-gin.txt)
|
||||
(MIT License) go-playground-locales (https://github.com/go-playground/locales) [link](./licenses/LICENSE-go-playground-locales.txt)
|
||||
(MIT License) go-playground-universal-translator (https://github.com/go-playground/universal-translator) [link](./licenses/LICENSE-go-playground-universal-translator.txt)
|
||||
(MIT License) go-playground-validator (https://github.com/go-playground/validator) [link](./licenses/LICENSE-go-playground-validator.txt)
|
||||
(MIT License) goccy-go-json (https://github.com/goccy/go-json) [link](./licenses/LICENSE-goccy-go-json.txt)
|
||||
(MIT License) jinzhu-copier (https://github.com/jinzhu/copier) [link](./licenses/LICENSE-jinzhu-copier.txt)
|
||||
(MIT License) jinzhu-now (https://github.com/jinzhu/now) [link](./licenses/LICENSE-jinzhu-now.txt)
|
||||
(MIT License) jordan-wright-email (https://github.com/jordan-wright/email) [link](./licenses/LICENSE-jordan-wright-email.txt)
|
||||
(MIT License) lib-pq (https://github.com/lib/pq) [link](./licenses/LICENSE-lib-pq.txt)
|
||||
(MIT License) mattn-go-sqlite3 (https://github.com/mattn/go-sqlite3) [link](./licenses/LICENSE-mattn-go-sqlite3.txt)
|
||||
(MIT License) segmentfault-pacman (https://github.com/segmentfault/pacman) [link](./licenses/LICENSE-segmentfault-pacman.txt)
|
||||
(MIT License) robfig-cron (https://github.com/robfig/cron) [link](./licenses/LICENSE-robfig-cron.txt)
|
||||
(MIT License) scottleedavis-go-exif-remove (https://github.com/scottleedavis/go-exif-remove) [link](./licenses/LICENSE-scottleedavis-go-exif-remove.txt)
|
||||
(MIT License) segmentfault-pacman (https://github.com/segmentfault/pacman) [link](./licenses/LICENSE-segmentfault-pacman.txt)
|
||||
(MIT License) stretchr-testify (https://github.com/stretchr/testify) [link](./licenses/LICENSE-stretchr-testify.txt)
|
||||
(MIT License) swaggo-files (https://github.com/swaggo/files) [link](./licenses/LICENSE-swaggo-files.txt)
|
||||
(MIT License) swaggo-gin-swagger (https://github.com/swaggo/gin-swagger) [link](./licenses/LICENSE-swaggo-gin-swagger.txt)
|
||||
(MIT License) swaggo-swag (https://github.com/swaggo/swag) [link](./licenses/LICENSE-swaggo-swag.txt)
|
||||
(MIT License) swr (https://github.com/vercel/swr) [link](./licenses/LICENSE-vercel-swr.txt)
|
||||
(MIT License) tidwall-gjson (https://github.com/tidwall/gjson) [link](./licenses/LICENSE-tidwall-gjson.txt)
|
||||
(MIT License) uuidjs-uuid (https://github.com/uuidjs/uuid) [link](./licenses/LICENSE-uuidjs-uuid.txt)
|
||||
(MIT License) yuin-goldmark (https://github.com/yuin/goldmark) [link](./licenses/LICENSE-yuin-goldmark.txt)
|
||||
(MIT License) go-gomail-gomail (https://gopkg.in/gomail.v2) [link](./licenses/LICENSE-go-gomail-gomail.txt)
|
||||
(MIT License) front-matter (https://github.com/jxson/front-matter) [link](./licenses/LICENSE-jxson-front-matter.txt)
|
||||
(MIT License) js-sha256 (https://github.com/emn178/js-sha256) [link](./licenses/LICENSE-emn178-js-sha256.txt)
|
||||
(MIT License) zustand (https://github.com/pmndrs/zustand) [link](./licenses/LICENSE-pmndrs-zustand.txt)
|
||||
|
||||
========================================================================
|
||||
BSD licenses
|
||||
@@ -286,13 +292,13 @@ BSD licenses
|
||||
The following components are provided under a BSD license. See project link for details.
|
||||
|
||||
(BSD 2-Clause) bwmarrin-snowflake (https://github.com/bwmarrin/snowflake) [link](./licenses/LICENSE-bwmarrin-snowflake.txt)
|
||||
(BSD 2-Clause) xorm (https://xorm.io/xorm) [link](./licenses/LICENSE-xorm.txt)
|
||||
(BSD 3-Clause) cznic-sqlite (https://modernc.org/sqlite) [link](./licenses/LICENSE-cznic-sqlite.txt)
|
||||
(BSD 3-Clause) google-uuid (https://github.com/google/uuid) [link](./licenses/LICENSE-google-uuid.txt)
|
||||
(BSD 3-Clause) grokify-html-strip-tags-go (https://github.com/grokify/html-strip-tags-go) [link](./licenses/LICENSE-grokify-html-strip-tags-go.txt)
|
||||
(BSD 3-Clause) microcosm-cc-bluemonday (https://github.com/microcosm-cc/bluemonday) [link](./licenses/LICENSE-microcosm-cc-bluemonday.txt)
|
||||
(BSD 3-Clause) cznic-sqlite (https://modernc.org/sqlite) [link](./licenses/LICENSE-cznic-sqlite.txt)
|
||||
(BSD 3-Clause) jsdiff (https://github.com/kpdecker/jsdiff) [link](./licenses/LICENSE-kpdecker-jsdiff.txt)
|
||||
(BSD 3-Clause) microcosm-cc-bluemonday (https://github.com/microcosm-cc/bluemonday) [link](./licenses/LICENSE-microcosm-cc-bluemonday.txt)
|
||||
(BSD 3-Clause) qs (https://github.com/ljharb/qs) [link](./licenses/LICENSE-ljharb-qs.txt)
|
||||
(BSD 2-Clause) xorm (https://xorm.io/xorm) [link](./licenses/LICENSE-xorm.txt)
|
||||
|
||||
========================================================================
|
||||
ISC licenses
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
Apache Answer
|
||||
Copyright 2025 The Apache Software Foundation
|
||||
Copyright 2023-2026 The Apache Software Foundation
|
||||
|
||||
This product includes software developed at
|
||||
The Apache Software Foundation (https://www.apache.org/).
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-present Jeevanandam M., https://myjeeva.com <jeeva@myjeeva.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,22 @@
|
||||
Copyright (c) 2013 John Barton
|
||||
|
||||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Anthropic, PBC
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 mozillazg
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,9 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2010-2020 Robert Kieffer and other contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
+1659
-256
File diff suppressed because it is too large
Load Diff
+1025
-176
File diff suppressed because it is too large
Load Diff
@@ -17,7 +17,7 @@
|
||||
|
||||
module github.com/apache/answer
|
||||
|
||||
go 1.23.0
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
github.com/Machiel/slugify v1.0.1
|
||||
@@ -30,6 +30,7 @@ require (
|
||||
github.com/go-playground/locales v0.14.1
|
||||
github.com/go-playground/universal-translator v0.18.1
|
||||
github.com/go-playground/validator/v10 v10.22.1
|
||||
github.com/go-resty/resty/v2 v2.17.1
|
||||
github.com/go-sql-driver/mysql v1.8.1
|
||||
github.com/goccy/go-json v0.10.3
|
||||
github.com/google/uuid v1.6.0
|
||||
@@ -37,11 +38,15 @@ require (
|
||||
github.com/grokify/html-strip-tags-go v0.1.0
|
||||
github.com/jinzhu/copier v0.4.0
|
||||
github.com/jinzhu/now v1.1.5
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/mark3labs/mcp-go v0.43.2
|
||||
github.com/microcosm-cc/bluemonday v1.0.27
|
||||
github.com/mozillazg/go-pinyin v0.20.0
|
||||
github.com/mozillazg/go-unidecode v0.2.0
|
||||
github.com/ory/dockertest/v3 v3.11.0
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/sashabaranov/go-openai v1.41.2
|
||||
github.com/scottleedavis/go-exif-remove v0.0.0-20230314195146-7e059d593405
|
||||
github.com/segmentfault/pacman v1.0.5-0.20230822083413-c0075a2d401f
|
||||
github.com/segmentfault/pacman/contrib/cache/memory v0.0.0-20230822083413-c0075a2d401f
|
||||
@@ -56,12 +61,11 @@ require (
|
||||
github.com/swaggo/swag v1.16.3
|
||||
github.com/tidwall/gjson v1.17.3
|
||||
github.com/yuin/goldmark v1.7.4
|
||||
go.uber.org/mock v0.5.0
|
||||
golang.org/x/crypto v0.36.0
|
||||
go.uber.org/mock v0.6.0
|
||||
golang.org/x/crypto v0.53.0
|
||||
golang.org/x/image v0.20.0
|
||||
golang.org/x/net v0.38.0
|
||||
golang.org/x/term v0.30.0
|
||||
golang.org/x/text v0.23.0
|
||||
golang.org/x/term v0.44.0
|
||||
golang.org/x/text v0.39.0
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
modernc.org/sqlite v1.33.0
|
||||
@@ -79,6 +83,8 @@ require (
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
|
||||
github.com/andybalholm/brotli v1.1.0 // indirect
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/buger/jsonparser v1.1.1 // indirect
|
||||
github.com/bytedance/sonic v1.12.2 // indirect
|
||||
github.com/bytedance/sonic/loader v0.2.0 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
@@ -117,6 +123,7 @@ require (
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/invopop/jsonschema v0.13.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
|
||||
@@ -145,7 +152,7 @@ require (
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/spf13/cast v1.7.0 // indirect
|
||||
github.com/spf13/cast v1.7.1 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/viper v1.19.0 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
@@ -154,15 +161,18 @@ require (
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
golang.org/x/arch v0.10.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect
|
||||
golang.org/x/sys v0.31.0 // indirect
|
||||
golang.org/x/tools v0.25.0 // indirect
|
||||
golang.org/x/net v0.56.0 // indirect
|
||||
golang.org/x/sys v0.46.0 // indirect
|
||||
golang.org/x/tools v0.47.0 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
|
||||
@@ -54,10 +54,14 @@ github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN
|
||||
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
|
||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
||||
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
|
||||
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
|
||||
github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0=
|
||||
github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE=
|
||||
github.com/bytedance/sonic v1.12.2 h1:oaMFuRTpMHYLpCntGca65YWt5ny+wAceDERTkT2L9lg=
|
||||
@@ -197,6 +201,8 @@ github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91
|
||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||
github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA=
|
||||
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/go-resty/resty/v2 v2.17.1 h1:x3aMpHK1YM9e4va/TMDRlusDDoZiQ+ViDu/WpA6xTM4=
|
||||
github.com/go-resty/resty/v2 v2.17.1/go.mod h1:kCKZ3wWmwJaNc7S29BRtUhJwy7iqmn+2mLtQrOyQlVA=
|
||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||
@@ -301,6 +307,8 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
|
||||
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
|
||||
github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
|
||||
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
|
||||
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
||||
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
||||
@@ -354,6 +362,8 @@ github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/jonboulle/clockwork v0.3.0 h1:9BSCMi8C+0qdApAp4auwX0RkLGUjs956h0EkuQymUhg=
|
||||
github.com/jonboulle/clockwork v0.3.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
|
||||
@@ -408,6 +418,8 @@ github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0V
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mark3labs/mcp-go v0.43.2 h1:21PUSlWWiSbUPQwXIJ5WKlETixpFpq+WBpbMGDSVy/I=
|
||||
github.com/mark3labs/mcp-go v0.43.2/go.mod h1:YnJfOL382MIWDx1kMY+2zsRHU/q78dBg9aFb8W6Thdw=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
@@ -450,6 +462,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/mozillazg/go-pinyin v0.20.0 h1:BtR3DsxpApHfKReaPO1fCqF4pThRwH9uwvXzm+GnMFQ=
|
||||
github.com/mozillazg/go-pinyin v0.20.0/go.mod h1:iR4EnMMRXkfpFVV5FMi4FNB6wGq9NV6uDWbUuPhP4Yc=
|
||||
github.com/mozillazg/go-unidecode v0.2.0 h1:vFGEzAH9KSwyWmXCOblazEWDh7fOkpmy/Z4ArmamSUc=
|
||||
github.com/mozillazg/go-unidecode v0.2.0/go.mod h1:zB48+/Z5toiRolOZy9ksLryJ976VIwmDmpQ2quyt1aA=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
|
||||
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
|
||||
@@ -541,6 +555,8 @@ github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
||||
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
|
||||
github.com/sashabaranov/go-openai v1.41.2 h1:vfPRBZNMpnqu8ELsclWcAvF19lDNgh1t6TVfFFOPiSM=
|
||||
github.com/sashabaranov/go-openai v1.41.2/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/scottleedavis/go-exif-remove v0.0.0-20230314195146-7e059d593405 h1:2ieGkj4z/YPXVyQ2ayZUg3GwE1pYWd5f1RB6DzAOXKM=
|
||||
github.com/scottleedavis/go-exif-remove v0.0.0-20230314195146-7e059d593405/go.mod h1:rIxVzVLKlBwLxO+lC+k/I4HJfRQcemg/f/76Xmmzsec=
|
||||
@@ -574,8 +590,8 @@ github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9yS
|
||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
|
||||
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
|
||||
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||
@@ -628,6 +644,8 @@ github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65E
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
@@ -636,6 +654,8 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
@@ -654,8 +674,8 @@ go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
||||
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
||||
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||
@@ -685,8 +705,8 @@ golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWP
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
|
||||
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk=
|
||||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY=
|
||||
@@ -703,8 +723,8 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
|
||||
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
|
||||
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -731,8 +751,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
|
||||
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
|
||||
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -743,8 +763,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
||||
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
|
||||
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -779,14 +799,14 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
|
||||
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
|
||||
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
|
||||
golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
|
||||
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
@@ -795,10 +815,12 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||
golang.org/x/text v0.39.0 h1:UbZz4pLOvn600D6Oh6GGEI6VAmndrEBLv8/6BEXzyus=
|
||||
golang.org/x/text v0.39.0/go.mod h1:3UwRclnC2g0TU9x8PZiyfOajCd1zaUNHF9cvqcQZ+ZM=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -820,8 +842,8 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY
|
||||
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE=
|
||||
golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg=
|
||||
golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q=
|
||||
golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA=
|
||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
+96
-5
@@ -58,7 +58,7 @@ backend:
|
||||
undelete:
|
||||
other: Obnovit
|
||||
merge:
|
||||
other: Merge
|
||||
other: Sloučit
|
||||
role:
|
||||
name:
|
||||
user:
|
||||
@@ -234,7 +234,7 @@ backend:
|
||||
other: Nemáte oprávnění pro aktualizaci.
|
||||
content_cannot_empty:
|
||||
other: Content cannot be empty.
|
||||
content_less_than_minumum:
|
||||
content_less_than_minimum:
|
||||
other: Not enough content entered.
|
||||
rank:
|
||||
fail_to_meet_the_condition:
|
||||
@@ -846,6 +846,16 @@ ui:
|
||||
http_403: HTTP Error 403
|
||||
logout: Log Out
|
||||
posts: Posts
|
||||
ai_assistant: AI Assistant
|
||||
ai_assistant:
|
||||
description: Got a question? Ask it and get answers, perspectives, and recommendations.
|
||||
recent_conversations: Recent Conversations
|
||||
show_more: Show more
|
||||
new: New chat
|
||||
ai_generate: AI-generated from posts and may not be accurate.
|
||||
copy: Copy
|
||||
ask_a_follow_up: Ask a follow-up
|
||||
ask_placeholder: Ask a question
|
||||
notifications:
|
||||
title: Notifications
|
||||
inbox: Inbox
|
||||
@@ -1190,8 +1200,7 @@ ui:
|
||||
search:
|
||||
placeholder: Search
|
||||
footer:
|
||||
build_on: >-
|
||||
Powered by <1> Apache Answer </1>- the open-source software that powers Q&A communities.<br />Made with love © {{cc}}.
|
||||
build_on: Powered by <1> Apache Answer </1>
|
||||
upload_img:
|
||||
name: Change
|
||||
loading: loading...
|
||||
@@ -1768,6 +1777,7 @@ ui:
|
||||
branding: Branding
|
||||
legal: Legal
|
||||
write: Write
|
||||
terms: Terms
|
||||
tos: Terms of Service
|
||||
privacy: Privacy
|
||||
seo: SEO
|
||||
@@ -1778,6 +1788,18 @@ ui:
|
||||
plugins: Plugins
|
||||
installed_plugins: Installed Plugins
|
||||
apperance: Appearance
|
||||
community: Community
|
||||
advanced: Advanced
|
||||
tags: Tags
|
||||
rules: Rules
|
||||
policies: Policies
|
||||
security: Security
|
||||
files: Files
|
||||
apikeys: API Keys
|
||||
intelligence: Intelligence
|
||||
ai_assistant: AI Assistant
|
||||
ai_settings: AI Settings
|
||||
mcp: MCP
|
||||
website_welcome: Welcome to {{site_name}}
|
||||
user_center:
|
||||
login: Login
|
||||
@@ -2089,7 +2111,7 @@ ui:
|
||||
always_display: Always display external content
|
||||
ask_before_display: Ask before displaying external content
|
||||
write:
|
||||
page_title: Write
|
||||
page_title: Files
|
||||
min_content:
|
||||
label: Minimum question body length
|
||||
text: Minimum allowed question body length in characters.
|
||||
@@ -2147,6 +2169,10 @@ ui:
|
||||
primary_color:
|
||||
label: Primary color
|
||||
text: Modify the colors used by your themes
|
||||
layout:
|
||||
label: Layout
|
||||
full_width: Full-width
|
||||
fixed_width: Fixed-width
|
||||
css_and_html:
|
||||
page_title: CSS and HTML
|
||||
custom_css:
|
||||
@@ -2251,6 +2277,70 @@ ui:
|
||||
show_logs: Show logs
|
||||
status: Status
|
||||
title: Badges
|
||||
apikeys:
|
||||
title: API Keys
|
||||
add_api_key: Add API Key
|
||||
desc: Description
|
||||
scope: Scope
|
||||
key: Key
|
||||
created: Created
|
||||
last_used: Last used
|
||||
add_or_edit_modal:
|
||||
add_title: Add API Key
|
||||
edit_title: Edit API Key
|
||||
description: Description
|
||||
description_required: Description is required.
|
||||
scope: Scope
|
||||
global: Global
|
||||
read-only: Read-only
|
||||
created_modal:
|
||||
title: API key created
|
||||
api_key: API key
|
||||
description: This key will not be displayed again. Make sure you take a copy before continuing.
|
||||
delete_modal:
|
||||
title: Delete API Key
|
||||
content: Any applications or scripts using this key will no longer be able to access the API. This is permanent!
|
||||
ai_settings:
|
||||
enabled:
|
||||
label: AI enabled
|
||||
check: Enable AI features
|
||||
text: The AI model must be configured correctly before it can be used.
|
||||
provider:
|
||||
label: Provider
|
||||
api_host:
|
||||
label: API host
|
||||
msg: API host is required
|
||||
api_key:
|
||||
label: API key
|
||||
check: Check
|
||||
check_success: "Connection successful."
|
||||
msg: API key is required
|
||||
model:
|
||||
label: Model
|
||||
msg: Model is required
|
||||
add_success: AI settings updated successfully.
|
||||
conversations:
|
||||
topic: Topic
|
||||
helpful: Helpful
|
||||
unhelpful: Unhelpful
|
||||
created: Created
|
||||
action: Action
|
||||
empty: No conversations found.
|
||||
delete_modal:
|
||||
title: Delete conversation
|
||||
content: Are you sure you want to delete this conversation? This is permanent!
|
||||
delete_success: Conversation deleted successfully.
|
||||
mcp:
|
||||
mcp_server:
|
||||
label: MCP server
|
||||
switch: Enabled
|
||||
type:
|
||||
label: Type
|
||||
url:
|
||||
label: URL
|
||||
http_header:
|
||||
label: HTTP header
|
||||
text: Please replace {key} with the API Key.
|
||||
form:
|
||||
optional: (optional)
|
||||
empty: cannot be empty
|
||||
@@ -2347,6 +2437,7 @@ ui:
|
||||
user_normal: This user is already normal.
|
||||
user_suspended: This user has been suspended.
|
||||
user_deleted: This user has been deleted.
|
||||
user_added: User has been added successfully.
|
||||
badge_activated: This badge has been activated.
|
||||
badge_inactivated: This badge has been inactivated.
|
||||
users_deleted: These users have been deleted.
|
||||
|
||||
+95
-4
@@ -234,7 +234,7 @@ backend:
|
||||
other: Dim caniatâd i ddiweddaru.
|
||||
content_cannot_empty:
|
||||
other: Content cannot be empty.
|
||||
content_less_than_minumum:
|
||||
content_less_than_minimum:
|
||||
other: Not enough content entered.
|
||||
rank:
|
||||
fail_to_meet_the_condition:
|
||||
@@ -846,6 +846,16 @@ ui:
|
||||
http_403: Gwall HTTP 403
|
||||
logout: Log Out
|
||||
posts: Posts
|
||||
ai_assistant: AI Assistant
|
||||
ai_assistant:
|
||||
description: Got a question? Ask it and get answers, perspectives, and recommendations.
|
||||
recent_conversations: Recent Conversations
|
||||
show_more: Show more
|
||||
new: New chat
|
||||
ai_generate: AI-generated from posts and may not be accurate.
|
||||
copy: Copy
|
||||
ask_a_follow_up: Ask a follow-up
|
||||
ask_placeholder: Ask a question
|
||||
notifications:
|
||||
title: Hysbysiadau
|
||||
inbox: Mewnflwch
|
||||
@@ -1190,8 +1200,7 @@ ui:
|
||||
search:
|
||||
placeholder: Search
|
||||
footer:
|
||||
build_on: >-
|
||||
Powered by <1> Apache Answer </1>- the open-source software that powers Q&A communities.<br />Made with love © {{cc}}.
|
||||
build_on: Powered by <1> Apache Answer </1>
|
||||
upload_img:
|
||||
name: Change
|
||||
loading: loading...
|
||||
@@ -1768,6 +1777,7 @@ ui:
|
||||
branding: Branding
|
||||
legal: Legal
|
||||
write: Write
|
||||
terms: Terms
|
||||
tos: Terms of Service
|
||||
privacy: Privacy
|
||||
seo: SEO
|
||||
@@ -1778,6 +1788,18 @@ ui:
|
||||
plugins: Plugins
|
||||
installed_plugins: Installed Plugins
|
||||
apperance: Appearance
|
||||
community: Community
|
||||
advanced: Advanced
|
||||
tags: Tags
|
||||
rules: Rules
|
||||
policies: Policies
|
||||
security: Security
|
||||
files: Files
|
||||
apikeys: API Keys
|
||||
intelligence: Intelligence
|
||||
ai_assistant: AI Assistant
|
||||
ai_settings: AI Settings
|
||||
mcp: MCP
|
||||
website_welcome: Welcome to {{site_name}}
|
||||
user_center:
|
||||
login: Login
|
||||
@@ -2089,7 +2111,7 @@ ui:
|
||||
always_display: Always display external content
|
||||
ask_before_display: Ask before displaying external content
|
||||
write:
|
||||
page_title: Write
|
||||
page_title: Files
|
||||
min_content:
|
||||
label: Minimum question body length
|
||||
text: Minimum allowed question body length in characters.
|
||||
@@ -2147,6 +2169,10 @@ ui:
|
||||
primary_color:
|
||||
label: Primary color
|
||||
text: Modify the colors used by your themes
|
||||
layout:
|
||||
label: Layout
|
||||
full_width: Full-width
|
||||
fixed_width: Fixed-width
|
||||
css_and_html:
|
||||
page_title: CSS and HTML
|
||||
custom_css:
|
||||
@@ -2251,6 +2277,70 @@ ui:
|
||||
show_logs: Show logs
|
||||
status: Status
|
||||
title: Badges
|
||||
apikeys:
|
||||
title: API Keys
|
||||
add_api_key: Add API Key
|
||||
desc: Description
|
||||
scope: Scope
|
||||
key: Key
|
||||
created: Created
|
||||
last_used: Last used
|
||||
add_or_edit_modal:
|
||||
add_title: Add API Key
|
||||
edit_title: Edit API Key
|
||||
description: Description
|
||||
description_required: Description is required.
|
||||
scope: Scope
|
||||
global: Global
|
||||
read-only: Read-only
|
||||
created_modal:
|
||||
title: API key created
|
||||
api_key: API key
|
||||
description: This key will not be displayed again. Make sure you take a copy before continuing.
|
||||
delete_modal:
|
||||
title: Delete API Key
|
||||
content: Any applications or scripts using this key will no longer be able to access the API. This is permanent!
|
||||
ai_settings:
|
||||
enabled:
|
||||
label: AI enabled
|
||||
check: Enable AI features
|
||||
text: The AI model must be configured correctly before it can be used.
|
||||
provider:
|
||||
label: Provider
|
||||
api_host:
|
||||
label: API host
|
||||
msg: API host is required
|
||||
api_key:
|
||||
label: API key
|
||||
check: Check
|
||||
check_success: "Connection successful."
|
||||
msg: API key is required
|
||||
model:
|
||||
label: Model
|
||||
msg: Model is required
|
||||
add_success: AI settings updated successfully.
|
||||
conversations:
|
||||
topic: Topic
|
||||
helpful: Helpful
|
||||
unhelpful: Unhelpful
|
||||
created: Created
|
||||
action: Action
|
||||
empty: No conversations found.
|
||||
delete_modal:
|
||||
title: Delete conversation
|
||||
content: Are you sure you want to delete this conversation? This is permanent!
|
||||
delete_success: Conversation deleted successfully.
|
||||
mcp:
|
||||
mcp_server:
|
||||
label: MCP server
|
||||
switch: Enabled
|
||||
type:
|
||||
label: Type
|
||||
url:
|
||||
label: URL
|
||||
http_header:
|
||||
label: HTTP header
|
||||
text: Please replace {key} with the API Key.
|
||||
form:
|
||||
optional: (optional)
|
||||
empty: cannot be empty
|
||||
@@ -2347,6 +2437,7 @@ ui:
|
||||
user_normal: This user is already normal.
|
||||
user_suspended: This user has been suspended.
|
||||
user_deleted: This user has been deleted.
|
||||
user_added: User has been added successfully.
|
||||
badge_activated: This badge has been activated.
|
||||
badge_inactivated: This badge has been inactivated.
|
||||
users_deleted: These users have been deleted.
|
||||
|
||||
+438
-347
File diff suppressed because it is too large
Load Diff
+95
-4
@@ -234,7 +234,7 @@ backend:
|
||||
other: Keine Berechtigung zum Aktualisieren.
|
||||
content_cannot_empty:
|
||||
other: Der Inhalt darf nicht leer sein.
|
||||
content_less_than_minumum:
|
||||
content_less_than_minimum:
|
||||
other: Not enough content entered.
|
||||
rank:
|
||||
fail_to_meet_the_condition:
|
||||
@@ -846,6 +846,16 @@ ui:
|
||||
http_403: HTTP Fehler 403
|
||||
logout: Ausloggen
|
||||
posts: Posts
|
||||
ai_assistant: AI Assistant
|
||||
ai_assistant:
|
||||
description: Got a question? Ask it and get answers, perspectives, and recommendations.
|
||||
recent_conversations: Recent Conversations
|
||||
show_more: Show more
|
||||
new: New chat
|
||||
ai_generate: AI-generated from posts and may not be accurate.
|
||||
copy: Copy
|
||||
ask_a_follow_up: Ask a follow-up
|
||||
ask_placeholder: Ask a question
|
||||
notifications:
|
||||
title: Benachrichtigungen
|
||||
inbox: Posteingang
|
||||
@@ -1190,8 +1200,7 @@ ui:
|
||||
search:
|
||||
placeholder: Suchen
|
||||
footer:
|
||||
build_on: >-
|
||||
Betrieben von <1> Apache Answer </1>- die Open-Source-Software, die Q&A-Communities betreibt.<br />Made with love © {{cc}}.
|
||||
build_on: Powered by <1> Apache Answer </1>
|
||||
upload_img:
|
||||
name: Ändern
|
||||
loading: wird geladen...
|
||||
@@ -1768,6 +1777,7 @@ ui:
|
||||
branding: Branding
|
||||
legal: Rechtliches
|
||||
write: Schreiben
|
||||
terms: Terms
|
||||
tos: Nutzungsbedingungen
|
||||
privacy: Privatsphäre
|
||||
seo: SEO
|
||||
@@ -1778,6 +1788,18 @@ ui:
|
||||
plugins: Erweiterungen (Plugins)
|
||||
installed_plugins: Installierte Plugins
|
||||
apperance: Erscheinungsbild
|
||||
community: Community
|
||||
advanced: Advanced
|
||||
tags: Tags
|
||||
rules: Rules
|
||||
policies: Policies
|
||||
security: Security
|
||||
files: Files
|
||||
apikeys: API Keys
|
||||
intelligence: Intelligence
|
||||
ai_assistant: AI Assistant
|
||||
ai_settings: AI Settings
|
||||
mcp: MCP
|
||||
website_welcome: Willkommen auf {{site_name}}
|
||||
user_center:
|
||||
login: Anmelden
|
||||
@@ -2089,7 +2111,7 @@ ui:
|
||||
always_display: Externen Inhalt immer anzeigen
|
||||
ask_before_display: Vor der Anzeige externer Inhalte fragen
|
||||
write:
|
||||
page_title: Schreiben
|
||||
page_title: Files
|
||||
min_content:
|
||||
label: Minimum question body length
|
||||
text: Minimum allowed question body length in characters.
|
||||
@@ -2147,6 +2169,10 @@ ui:
|
||||
primary_color:
|
||||
label: Primäre Farbe
|
||||
text: Ändere die Farben, die von deinen Themes verwendet werden
|
||||
layout:
|
||||
label: Layout
|
||||
full_width: Full-width
|
||||
fixed_width: Fixed-width
|
||||
css_and_html:
|
||||
page_title: CSS und HTML
|
||||
custom_css:
|
||||
@@ -2251,6 +2277,70 @@ ui:
|
||||
show_logs: Protokolle anzeigen
|
||||
status: Status
|
||||
title: Abzeichen
|
||||
apikeys:
|
||||
title: API Keys
|
||||
add_api_key: Add API Key
|
||||
desc: Description
|
||||
scope: Scope
|
||||
key: Key
|
||||
created: Created
|
||||
last_used: Last used
|
||||
add_or_edit_modal:
|
||||
add_title: Add API Key
|
||||
edit_title: Edit API Key
|
||||
description: Description
|
||||
description_required: Description is required.
|
||||
scope: Scope
|
||||
global: Global
|
||||
read-only: Read-only
|
||||
created_modal:
|
||||
title: API key created
|
||||
api_key: API key
|
||||
description: This key will not be displayed again. Make sure you take a copy before continuing.
|
||||
delete_modal:
|
||||
title: Delete API Key
|
||||
content: Any applications or scripts using this key will no longer be able to access the API. This is permanent!
|
||||
ai_settings:
|
||||
enabled:
|
||||
label: AI enabled
|
||||
check: Enable AI features
|
||||
text: The AI model must be configured correctly before it can be used.
|
||||
provider:
|
||||
label: Provider
|
||||
api_host:
|
||||
label: API host
|
||||
msg: API host is required
|
||||
api_key:
|
||||
label: API key
|
||||
check: Check
|
||||
check_success: "Connection successful."
|
||||
msg: API key is required
|
||||
model:
|
||||
label: Model
|
||||
msg: Model is required
|
||||
add_success: AI settings updated successfully.
|
||||
conversations:
|
||||
topic: Topic
|
||||
helpful: Helpful
|
||||
unhelpful: Unhelpful
|
||||
created: Created
|
||||
action: Action
|
||||
empty: No conversations found.
|
||||
delete_modal:
|
||||
title: Delete conversation
|
||||
content: Are you sure you want to delete this conversation? This is permanent!
|
||||
delete_success: Conversation deleted successfully.
|
||||
mcp:
|
||||
mcp_server:
|
||||
label: MCP server
|
||||
switch: Enabled
|
||||
type:
|
||||
label: Type
|
||||
url:
|
||||
label: URL
|
||||
http_header:
|
||||
label: HTTP header
|
||||
text: Please replace {key} with the API Key.
|
||||
form:
|
||||
optional: (optional)
|
||||
empty: kann nicht leer sein
|
||||
@@ -2347,6 +2437,7 @@ ui:
|
||||
user_normal: Dieser Benutzer ist bereits normal.
|
||||
user_suspended: Dieser Nutzer wurde gesperrt.
|
||||
user_deleted: Benutzer wurde gelöscht.
|
||||
user_added: User has been added successfully.
|
||||
badge_activated: Dieses Abzeichen wurde aktiviert.
|
||||
badge_inactivated: Dieses Abzeichen wurde deaktiviert.
|
||||
users_deleted: Der Benutzer wurde gelöscht.
|
||||
|
||||
+101
-6
@@ -235,7 +235,7 @@ backend:
|
||||
other: No permission to update.
|
||||
content_cannot_empty:
|
||||
other: Content cannot be empty.
|
||||
content_less_than_minumum:
|
||||
content_less_than_minimum:
|
||||
other: Not enough content entered.
|
||||
rank:
|
||||
fail_to_meet_the_condition:
|
||||
@@ -857,6 +857,18 @@ ui:
|
||||
http_403: HTTP Error 403
|
||||
logout: Log Out
|
||||
posts: Posts
|
||||
ai_assistant: AI Assistant
|
||||
ai_assistant:
|
||||
description: Got a question? Ask it and get answers, perspectives, and recommendations.
|
||||
recent_conversations: Recent Conversations
|
||||
show_more: Show more
|
||||
new: New chat
|
||||
ai_generate: AI-generated from posts and may not be accurate.
|
||||
copy: Copy
|
||||
ask_a_follow_up: Ask a follow-up
|
||||
ask_placeholder: Ask a question
|
||||
thinking: Thinking…
|
||||
thoughts: Thoughts
|
||||
notifications:
|
||||
title: Notifications
|
||||
inbox: Inbox
|
||||
@@ -1208,9 +1220,7 @@ ui:
|
||||
search:
|
||||
placeholder: Search
|
||||
footer:
|
||||
build_on: >-
|
||||
Powered by <1> Apache Answer </1>- the open-source software that powers Q&A
|
||||
communities.<br />Made with love © {{cc}}.
|
||||
build_on: Powered by <1> Apache Answer </1>
|
||||
upload_img:
|
||||
name: Change
|
||||
loading: loading...
|
||||
@@ -1807,6 +1817,7 @@ ui:
|
||||
branding: Branding
|
||||
legal: Legal
|
||||
write: Write
|
||||
terms: Terms
|
||||
tos: Terms of Service
|
||||
privacy: Privacy
|
||||
seo: SEO
|
||||
@@ -1817,6 +1828,18 @@ ui:
|
||||
plugins: Plugins
|
||||
installed_plugins: Installed Plugins
|
||||
apperance: Appearance
|
||||
community: Community
|
||||
advanced: Advanced
|
||||
tags: Tags
|
||||
rules: Rules
|
||||
policies: Policies
|
||||
security: Security
|
||||
files: Files
|
||||
apikeys: API Keys
|
||||
intelligence: Intelligence
|
||||
ai_assistant: AI Assistant
|
||||
ai_settings: AI Settings
|
||||
mcp: MCP
|
||||
website_welcome: Welcome to {{site_name}}
|
||||
user_center:
|
||||
login: Login
|
||||
@@ -2129,7 +2152,7 @@ ui:
|
||||
always_display: Always display external content
|
||||
ask_before_display: Ask before displaying external content
|
||||
write:
|
||||
page_title: Write
|
||||
page_title: Files
|
||||
min_content:
|
||||
label: Minimum question body length
|
||||
text: Minimum allowed question body length in characters.
|
||||
@@ -2187,6 +2210,10 @@ ui:
|
||||
primary_color:
|
||||
label: Primary color
|
||||
text: Modify the colors used by your themes
|
||||
layout:
|
||||
label: Layout
|
||||
full_width: Full-width
|
||||
fixed_width: Fixed-width
|
||||
css_and_html:
|
||||
page_title: CSS and HTML
|
||||
custom_css:
|
||||
@@ -2214,6 +2241,10 @@ ui:
|
||||
title: Email registration
|
||||
label: Allow email registration
|
||||
text: Turn off to prevent anyone creating new account through email.
|
||||
email_verification:
|
||||
title: Email verification
|
||||
label: Require email verification
|
||||
text: When enabled, users must verify their email address before using the site.
|
||||
allowed_email_domains:
|
||||
title: Allowed email domains
|
||||
text: Email domains that users must register accounts with. One domain per line. Ignored when empty.
|
||||
@@ -2288,6 +2319,70 @@ ui:
|
||||
show_logs: Show logs
|
||||
status: Status
|
||||
title: Badges
|
||||
apikeys:
|
||||
title: API Keys
|
||||
add_api_key: Add API Key
|
||||
desc: Description
|
||||
scope: Scope
|
||||
key: Key
|
||||
created: Created
|
||||
last_used: Last used
|
||||
add_or_edit_modal:
|
||||
add_title: Add API Key
|
||||
edit_title: Edit API Key
|
||||
description: Description
|
||||
description_required: Description is required.
|
||||
scope: Scope
|
||||
global: Global
|
||||
read-only: Read-only
|
||||
created_modal:
|
||||
title: API key created
|
||||
api_key: API key
|
||||
description: This key will not be displayed again. Make sure you take a copy before continuing.
|
||||
delete_modal:
|
||||
title: Delete API Key
|
||||
content: Any applications or scripts using this key will no longer be able to access the API. This is permanent!
|
||||
ai_settings:
|
||||
enabled:
|
||||
label: AI enabled
|
||||
check: Enable AI features
|
||||
text: The AI model must be configured correctly before it can be used.
|
||||
provider:
|
||||
label: Provider
|
||||
api_host:
|
||||
label: API host
|
||||
msg: API host is required
|
||||
api_key:
|
||||
label: API key
|
||||
check: Check
|
||||
check_success: "Connection successful."
|
||||
msg: API key is required
|
||||
model:
|
||||
label: Model
|
||||
msg: Model is required
|
||||
add_success: AI settings updated successfully.
|
||||
conversations:
|
||||
topic: Topic
|
||||
helpful: Helpful
|
||||
unhelpful: Unhelpful
|
||||
created: Created
|
||||
action: Action
|
||||
empty: No conversations found.
|
||||
delete_modal:
|
||||
title: Delete conversation
|
||||
content: Are you sure you want to delete this conversation? This is permanent!
|
||||
delete_success: Conversation deleted successfully.
|
||||
mcp:
|
||||
mcp_server:
|
||||
label: MCP server
|
||||
switch: Enabled
|
||||
type:
|
||||
label: Type
|
||||
url:
|
||||
label: URL
|
||||
http_header:
|
||||
label: HTTP header
|
||||
text: Please replace {key} with the API Key.
|
||||
form:
|
||||
optional: (optional)
|
||||
empty: cannot be empty
|
||||
@@ -2384,6 +2479,7 @@ ui:
|
||||
user_normal: This user is already normal.
|
||||
user_suspended: This user has been suspended.
|
||||
user_deleted: This user has been deleted.
|
||||
user_added: User has been added successfully.
|
||||
badge_activated: This badge has been activated.
|
||||
badge_inactivated: This badge has been inactivated.
|
||||
users_deleted: These users have been deleted.
|
||||
@@ -2393,4 +2489,3 @@ ui:
|
||||
copied: Copied
|
||||
external_content_warning: External images/media are not displayed.
|
||||
|
||||
|
||||
|
||||
+95
-4
@@ -234,7 +234,7 @@ backend:
|
||||
other: Sin permiso para actualizar.
|
||||
content_cannot_empty:
|
||||
other: Content cannot be empty.
|
||||
content_less_than_minumum:
|
||||
content_less_than_minimum:
|
||||
other: Not enough content entered.
|
||||
rank:
|
||||
fail_to_meet_the_condition:
|
||||
@@ -846,6 +846,16 @@ ui:
|
||||
http_403: HTTP Error 403
|
||||
logout: Cerrar sesión
|
||||
posts: Posts
|
||||
ai_assistant: AI Assistant
|
||||
ai_assistant:
|
||||
description: Got a question? Ask it and get answers, perspectives, and recommendations.
|
||||
recent_conversations: Recent Conversations
|
||||
show_more: Show more
|
||||
new: New chat
|
||||
ai_generate: AI-generated from posts and may not be accurate.
|
||||
copy: Copy
|
||||
ask_a_follow_up: Ask a follow-up
|
||||
ask_placeholder: Ask a question
|
||||
notifications:
|
||||
title: Notificaciones
|
||||
inbox: Buzón de entrada
|
||||
@@ -1190,8 +1200,7 @@ ui:
|
||||
search:
|
||||
placeholder: Buscar
|
||||
footer:
|
||||
build_on: >-
|
||||
Sitio creado por <1> Apache Answer </1>- el software libre que impulsa comunidades de Q&A.<br />Hecho con amor © {{cc}}.
|
||||
build_on: Powered by <1> Apache Answer </1>
|
||||
upload_img:
|
||||
name: Cambiar
|
||||
loading: cargando...
|
||||
@@ -1768,6 +1777,7 @@ ui:
|
||||
branding: Marca
|
||||
legal: Legal
|
||||
write: Escribir
|
||||
terms: Terms
|
||||
tos: Términos de servicio
|
||||
privacy: Privacidad
|
||||
seo: ESTE
|
||||
@@ -1778,6 +1788,18 @@ ui:
|
||||
plugins: Extensiones
|
||||
installed_plugins: Extensiones Instaladas
|
||||
apperance: Appearance
|
||||
community: Community
|
||||
advanced: Advanced
|
||||
tags: Tags
|
||||
rules: Rules
|
||||
policies: Policies
|
||||
security: Security
|
||||
files: Files
|
||||
apikeys: API Keys
|
||||
intelligence: Intelligence
|
||||
ai_assistant: AI Assistant
|
||||
ai_settings: AI Settings
|
||||
mcp: MCP
|
||||
website_welcome: Bienvenido a {{site_name}}
|
||||
user_center:
|
||||
login: Iniciar sesión
|
||||
@@ -2089,7 +2111,7 @@ ui:
|
||||
always_display: Always display external content
|
||||
ask_before_display: Ask before displaying external content
|
||||
write:
|
||||
page_title: Escribir
|
||||
page_title: Files
|
||||
min_content:
|
||||
label: Minimum question body length
|
||||
text: Minimum allowed question body length in characters.
|
||||
@@ -2147,6 +2169,10 @@ ui:
|
||||
primary_color:
|
||||
label: Color primario
|
||||
text: Modifica los colores usados por tus temas
|
||||
layout:
|
||||
label: Layout
|
||||
full_width: Full-width
|
||||
fixed_width: Fixed-width
|
||||
css_and_html:
|
||||
page_title: CSS y HTML
|
||||
custom_css:
|
||||
@@ -2251,6 +2277,70 @@ ui:
|
||||
show_logs: Mostrar logs
|
||||
status: Status
|
||||
title: Insignias
|
||||
apikeys:
|
||||
title: API Keys
|
||||
add_api_key: Add API Key
|
||||
desc: Description
|
||||
scope: Scope
|
||||
key: Key
|
||||
created: Created
|
||||
last_used: Last used
|
||||
add_or_edit_modal:
|
||||
add_title: Add API Key
|
||||
edit_title: Edit API Key
|
||||
description: Description
|
||||
description_required: Description is required.
|
||||
scope: Scope
|
||||
global: Global
|
||||
read-only: Read-only
|
||||
created_modal:
|
||||
title: API key created
|
||||
api_key: API key
|
||||
description: This key will not be displayed again. Make sure you take a copy before continuing.
|
||||
delete_modal:
|
||||
title: Delete API Key
|
||||
content: Any applications or scripts using this key will no longer be able to access the API. This is permanent!
|
||||
ai_settings:
|
||||
enabled:
|
||||
label: AI enabled
|
||||
check: Enable AI features
|
||||
text: The AI model must be configured correctly before it can be used.
|
||||
provider:
|
||||
label: Provider
|
||||
api_host:
|
||||
label: API host
|
||||
msg: API host is required
|
||||
api_key:
|
||||
label: API key
|
||||
check: Check
|
||||
check_success: "Connection successful."
|
||||
msg: API key is required
|
||||
model:
|
||||
label: Model
|
||||
msg: Model is required
|
||||
add_success: AI settings updated successfully.
|
||||
conversations:
|
||||
topic: Topic
|
||||
helpful: Helpful
|
||||
unhelpful: Unhelpful
|
||||
created: Created
|
||||
action: Action
|
||||
empty: No conversations found.
|
||||
delete_modal:
|
||||
title: Delete conversation
|
||||
content: Are you sure you want to delete this conversation? This is permanent!
|
||||
delete_success: Conversation deleted successfully.
|
||||
mcp:
|
||||
mcp_server:
|
||||
label: MCP server
|
||||
switch: Enabled
|
||||
type:
|
||||
label: Type
|
||||
url:
|
||||
label: URL
|
||||
http_header:
|
||||
label: HTTP header
|
||||
text: Please replace {key} with the API Key.
|
||||
form:
|
||||
optional: (opcional)
|
||||
empty: no puede estar en blanco
|
||||
@@ -2347,6 +2437,7 @@ ui:
|
||||
user_normal: Este usuario ya es normal.
|
||||
user_suspended: Este usuario ha sido suspendido.
|
||||
user_deleted: Este usuario ha sido eliminado.
|
||||
user_added: User has been added successfully.
|
||||
badge_activated: Esta insignia ha sido activada.
|
||||
badge_inactivated: Esta insignia ha sido desactivada.
|
||||
users_deleted: These users have been deleted.
|
||||
|
||||
+95
-4
@@ -234,7 +234,7 @@ backend:
|
||||
other: اجازه بروزرسانی ندارید.
|
||||
content_cannot_empty:
|
||||
other: Content cannot be empty.
|
||||
content_less_than_minumum:
|
||||
content_less_than_minimum:
|
||||
other: Not enough content entered.
|
||||
rank:
|
||||
fail_to_meet_the_condition:
|
||||
@@ -846,6 +846,16 @@ ui:
|
||||
http_403: خطای 403 HTTP
|
||||
logout: خروج
|
||||
posts: Posts
|
||||
ai_assistant: AI Assistant
|
||||
ai_assistant:
|
||||
description: Got a question? Ask it and get answers, perspectives, and recommendations.
|
||||
recent_conversations: Recent Conversations
|
||||
show_more: Show more
|
||||
new: New chat
|
||||
ai_generate: AI-generated from posts and may not be accurate.
|
||||
copy: Copy
|
||||
ask_a_follow_up: Ask a follow-up
|
||||
ask_placeholder: Ask a question
|
||||
notifications:
|
||||
title: اعلانات
|
||||
inbox: پیغامهای دریافتی
|
||||
@@ -1190,8 +1200,7 @@ ui:
|
||||
search:
|
||||
placeholder: جستجو
|
||||
footer:
|
||||
build_on: >-
|
||||
پشتیبانی شده توسط <1> Apache Answer </1> - نرمافزار منبع باز که باهمستان های پرسش و پاسخ را تقویت می کند.<br />ساخته شده با عشق © {{cc}}.
|
||||
build_on: Powered by <1> Apache Answer </1>
|
||||
upload_img:
|
||||
name: تغییر
|
||||
loading: درحال بارگذاری...
|
||||
@@ -1768,6 +1777,7 @@ ui:
|
||||
branding: نام تجاری
|
||||
legal: قانونی
|
||||
write: نوشتن
|
||||
terms: Terms
|
||||
tos: قوانین
|
||||
privacy: حریم خصوصی
|
||||
seo: سئو
|
||||
@@ -1778,6 +1788,18 @@ ui:
|
||||
plugins: افزونهها
|
||||
installed_plugins: پلاگین های نصب شده
|
||||
apperance: Appearance
|
||||
community: Community
|
||||
advanced: Advanced
|
||||
tags: Tags
|
||||
rules: Rules
|
||||
policies: Policies
|
||||
security: Security
|
||||
files: Files
|
||||
apikeys: API Keys
|
||||
intelligence: Intelligence
|
||||
ai_assistant: AI Assistant
|
||||
ai_settings: AI Settings
|
||||
mcp: MCP
|
||||
website_welcome: به {{site_name}} خوش آمدید
|
||||
user_center:
|
||||
login: ورود
|
||||
@@ -2089,7 +2111,7 @@ ui:
|
||||
always_display: Always display external content
|
||||
ask_before_display: Ask before displaying external content
|
||||
write:
|
||||
page_title: نوشتن
|
||||
page_title: Files
|
||||
min_content:
|
||||
label: Minimum question body length
|
||||
text: Minimum allowed question body length in characters.
|
||||
@@ -2147,6 +2169,10 @@ ui:
|
||||
primary_color:
|
||||
label: Primary color
|
||||
text: Modify the colors used by your themes
|
||||
layout:
|
||||
label: Layout
|
||||
full_width: Full-width
|
||||
fixed_width: Fixed-width
|
||||
css_and_html:
|
||||
page_title: CSS and HTML
|
||||
custom_css:
|
||||
@@ -2251,6 +2277,70 @@ ui:
|
||||
show_logs: Show logs
|
||||
status: Status
|
||||
title: Badges
|
||||
apikeys:
|
||||
title: API Keys
|
||||
add_api_key: Add API Key
|
||||
desc: Description
|
||||
scope: Scope
|
||||
key: Key
|
||||
created: Created
|
||||
last_used: Last used
|
||||
add_or_edit_modal:
|
||||
add_title: Add API Key
|
||||
edit_title: Edit API Key
|
||||
description: Description
|
||||
description_required: Description is required.
|
||||
scope: Scope
|
||||
global: Global
|
||||
read-only: Read-only
|
||||
created_modal:
|
||||
title: API key created
|
||||
api_key: API key
|
||||
description: This key will not be displayed again. Make sure you take a copy before continuing.
|
||||
delete_modal:
|
||||
title: Delete API Key
|
||||
content: Any applications or scripts using this key will no longer be able to access the API. This is permanent!
|
||||
ai_settings:
|
||||
enabled:
|
||||
label: AI enabled
|
||||
check: Enable AI features
|
||||
text: The AI model must be configured correctly before it can be used.
|
||||
provider:
|
||||
label: Provider
|
||||
api_host:
|
||||
label: API host
|
||||
msg: API host is required
|
||||
api_key:
|
||||
label: API key
|
||||
check: Check
|
||||
check_success: "Connection successful."
|
||||
msg: API key is required
|
||||
model:
|
||||
label: Model
|
||||
msg: Model is required
|
||||
add_success: AI settings updated successfully.
|
||||
conversations:
|
||||
topic: Topic
|
||||
helpful: Helpful
|
||||
unhelpful: Unhelpful
|
||||
created: Created
|
||||
action: Action
|
||||
empty: No conversations found.
|
||||
delete_modal:
|
||||
title: Delete conversation
|
||||
content: Are you sure you want to delete this conversation? This is permanent!
|
||||
delete_success: Conversation deleted successfully.
|
||||
mcp:
|
||||
mcp_server:
|
||||
label: MCP server
|
||||
switch: Enabled
|
||||
type:
|
||||
label: Type
|
||||
url:
|
||||
label: URL
|
||||
http_header:
|
||||
label: HTTP header
|
||||
text: Please replace {key} with the API Key.
|
||||
form:
|
||||
optional: (optional)
|
||||
empty: cannot be empty
|
||||
@@ -2347,6 +2437,7 @@ ui:
|
||||
user_normal: This user is already normal.
|
||||
user_suspended: This user has been suspended.
|
||||
user_deleted: This user has been deleted.
|
||||
user_added: User has been added successfully.
|
||||
badge_activated: This badge has been activated.
|
||||
badge_inactivated: This badge has been inactivated.
|
||||
users_deleted: These users have been deleted.
|
||||
|
||||
+95
-4
@@ -234,7 +234,7 @@ backend:
|
||||
other: Pas de permission pour mettre à jour.
|
||||
content_cannot_empty:
|
||||
other: Le contenu ne peut pas être vide.
|
||||
content_less_than_minumum:
|
||||
content_less_than_minimum:
|
||||
other: Not enough content entered.
|
||||
rank:
|
||||
fail_to_meet_the_condition:
|
||||
@@ -846,6 +846,16 @@ ui:
|
||||
http_403: Erreur HTTP 403
|
||||
logout: Se déconnecter
|
||||
posts: Posts
|
||||
ai_assistant: AI Assistant
|
||||
ai_assistant:
|
||||
description: Got a question? Ask it and get answers, perspectives, and recommendations.
|
||||
recent_conversations: Recent Conversations
|
||||
show_more: Show more
|
||||
new: New chat
|
||||
ai_generate: AI-generated from posts and may not be accurate.
|
||||
copy: Copy
|
||||
ask_a_follow_up: Ask a follow-up
|
||||
ask_placeholder: Ask a question
|
||||
notifications:
|
||||
title: Notifications
|
||||
inbox: Boîte de réception
|
||||
@@ -1190,8 +1200,7 @@ ui:
|
||||
search:
|
||||
placeholder: Rechercher
|
||||
footer:
|
||||
build_on: >-
|
||||
Propulsé par <1> Apache Answer </1>- le logiciel open-source qui alimente les communautés de Q&A.<br />Fait avec amour ©️ {{cc}}.
|
||||
build_on: Powered by <1> Apache Answer </1>
|
||||
upload_img:
|
||||
name: Remplacer
|
||||
loading: chargement en cours...
|
||||
@@ -1768,6 +1777,7 @@ ui:
|
||||
branding: Marque
|
||||
legal: Légal
|
||||
write: Écrire
|
||||
terms: Terms
|
||||
tos: Conditions d'utilisation
|
||||
privacy: Confidentialité
|
||||
seo: SEO
|
||||
@@ -1778,6 +1788,18 @@ ui:
|
||||
plugins: Extensions
|
||||
installed_plugins: Extensions installées
|
||||
apperance: Apparence
|
||||
community: Community
|
||||
advanced: Advanced
|
||||
tags: Tags
|
||||
rules: Rules
|
||||
policies: Policies
|
||||
security: Security
|
||||
files: Files
|
||||
apikeys: API Keys
|
||||
intelligence: Intelligence
|
||||
ai_assistant: AI Assistant
|
||||
ai_settings: AI Settings
|
||||
mcp: MCP
|
||||
website_welcome: Bienvenue sur {{site_name}}
|
||||
user_center:
|
||||
login: Connexion
|
||||
@@ -2089,7 +2111,7 @@ ui:
|
||||
always_display: Toujours afficher le contenu externe
|
||||
ask_before_display: Demander avant d'afficher le contenu externe
|
||||
write:
|
||||
page_title: Écrire
|
||||
page_title: Files
|
||||
min_content:
|
||||
label: Minimum question body length
|
||||
text: Minimum allowed question body length in characters.
|
||||
@@ -2147,6 +2169,10 @@ ui:
|
||||
primary_color:
|
||||
label: Couleur primaire
|
||||
text: Modifier les couleurs utilisées par vos thèmes
|
||||
layout:
|
||||
label: Layout
|
||||
full_width: Full-width
|
||||
fixed_width: Fixed-width
|
||||
css_and_html:
|
||||
page_title: CSS et HTML
|
||||
custom_css:
|
||||
@@ -2251,6 +2277,70 @@ ui:
|
||||
show_logs: Voir les logs
|
||||
status: Statut
|
||||
title: Badges
|
||||
apikeys:
|
||||
title: API Keys
|
||||
add_api_key: Add API Key
|
||||
desc: Description
|
||||
scope: Scope
|
||||
key: Key
|
||||
created: Created
|
||||
last_used: Last used
|
||||
add_or_edit_modal:
|
||||
add_title: Add API Key
|
||||
edit_title: Edit API Key
|
||||
description: Description
|
||||
description_required: Description is required.
|
||||
scope: Scope
|
||||
global: Global
|
||||
read-only: Read-only
|
||||
created_modal:
|
||||
title: API key created
|
||||
api_key: API key
|
||||
description: This key will not be displayed again. Make sure you take a copy before continuing.
|
||||
delete_modal:
|
||||
title: Delete API Key
|
||||
content: Any applications or scripts using this key will no longer be able to access the API. This is permanent!
|
||||
ai_settings:
|
||||
enabled:
|
||||
label: AI enabled
|
||||
check: Enable AI features
|
||||
text: The AI model must be configured correctly before it can be used.
|
||||
provider:
|
||||
label: Provider
|
||||
api_host:
|
||||
label: API host
|
||||
msg: API host is required
|
||||
api_key:
|
||||
label: API key
|
||||
check: Check
|
||||
check_success: "Connection successful."
|
||||
msg: API key is required
|
||||
model:
|
||||
label: Model
|
||||
msg: Model is required
|
||||
add_success: AI settings updated successfully.
|
||||
conversations:
|
||||
topic: Topic
|
||||
helpful: Helpful
|
||||
unhelpful: Unhelpful
|
||||
created: Created
|
||||
action: Action
|
||||
empty: No conversations found.
|
||||
delete_modal:
|
||||
title: Delete conversation
|
||||
content: Are you sure you want to delete this conversation? This is permanent!
|
||||
delete_success: Conversation deleted successfully.
|
||||
mcp:
|
||||
mcp_server:
|
||||
label: MCP server
|
||||
switch: Enabled
|
||||
type:
|
||||
label: Type
|
||||
url:
|
||||
label: URL
|
||||
http_header:
|
||||
label: HTTP header
|
||||
text: Please replace {key} with the API Key.
|
||||
form:
|
||||
optional: (optionnel)
|
||||
empty: ne peut pas être vide
|
||||
@@ -2347,6 +2437,7 @@ ui:
|
||||
user_normal: Cet utilisateur est déjà normal.
|
||||
user_suspended: Cet utilisateur a été suspendu.
|
||||
user_deleted: Cet utilisateur a été supprimé.
|
||||
user_added: User has been added successfully.
|
||||
badge_activated: Ce badge a été activé.
|
||||
badge_inactivated: Ce badge a été désactivé.
|
||||
users_deleted: Ces utilisateurs ont été supprimés.
|
||||
|
||||
+95
-4
@@ -234,7 +234,7 @@ backend:
|
||||
other: No permission to update.
|
||||
content_cannot_empty:
|
||||
other: Content cannot be empty.
|
||||
content_less_than_minumum:
|
||||
content_less_than_minimum:
|
||||
other: Not enough content entered.
|
||||
rank:
|
||||
fail_to_meet_the_condition:
|
||||
@@ -846,6 +846,16 @@ ui:
|
||||
http_403: HTTP Error 403
|
||||
logout: Log Out
|
||||
posts: Posts
|
||||
ai_assistant: AI Assistant
|
||||
ai_assistant:
|
||||
description: Got a question? Ask it and get answers, perspectives, and recommendations.
|
||||
recent_conversations: Recent Conversations
|
||||
show_more: Show more
|
||||
new: New chat
|
||||
ai_generate: AI-generated from posts and may not be accurate.
|
||||
copy: Copy
|
||||
ask_a_follow_up: Ask a follow-up
|
||||
ask_placeholder: Ask a question
|
||||
notifications:
|
||||
title: Notifications
|
||||
inbox: Inbox
|
||||
@@ -1190,8 +1200,7 @@ ui:
|
||||
search:
|
||||
placeholder: Search
|
||||
footer:
|
||||
build_on: >-
|
||||
Powered by <1> Apache Answer </1>- the open-source software that powers Q&A communities.<br />Made with love © {{cc}}.
|
||||
build_on: Powered by <1> Apache Answer </1>
|
||||
upload_img:
|
||||
name: Change
|
||||
loading: loading...
|
||||
@@ -1768,6 +1777,7 @@ ui:
|
||||
branding: Branding
|
||||
legal: Legal
|
||||
write: Write
|
||||
terms: Terms
|
||||
tos: Terms of Service
|
||||
privacy: Privacy
|
||||
seo: SEO
|
||||
@@ -1778,6 +1788,18 @@ ui:
|
||||
plugins: Plugins
|
||||
installed_plugins: Installed Plugins
|
||||
apperance: Appearance
|
||||
community: Community
|
||||
advanced: Advanced
|
||||
tags: Tags
|
||||
rules: Rules
|
||||
policies: Policies
|
||||
security: Security
|
||||
files: Files
|
||||
apikeys: API Keys
|
||||
intelligence: Intelligence
|
||||
ai_assistant: AI Assistant
|
||||
ai_settings: AI Settings
|
||||
mcp: MCP
|
||||
website_welcome: Welcome to {{site_name}}
|
||||
user_center:
|
||||
login: Login
|
||||
@@ -2089,7 +2111,7 @@ ui:
|
||||
always_display: Always display external content
|
||||
ask_before_display: Ask before displaying external content
|
||||
write:
|
||||
page_title: Write
|
||||
page_title: Files
|
||||
min_content:
|
||||
label: Minimum question body length
|
||||
text: Minimum allowed question body length in characters.
|
||||
@@ -2147,6 +2169,10 @@ ui:
|
||||
primary_color:
|
||||
label: Primary color
|
||||
text: Modify the colors used by your themes
|
||||
layout:
|
||||
label: Layout
|
||||
full_width: Full-width
|
||||
fixed_width: Fixed-width
|
||||
css_and_html:
|
||||
page_title: CSS and HTML
|
||||
custom_css:
|
||||
@@ -2251,6 +2277,70 @@ ui:
|
||||
show_logs: Show logs
|
||||
status: Status
|
||||
title: Badges
|
||||
apikeys:
|
||||
title: API Keys
|
||||
add_api_key: Add API Key
|
||||
desc: Description
|
||||
scope: Scope
|
||||
key: Key
|
||||
created: Created
|
||||
last_used: Last used
|
||||
add_or_edit_modal:
|
||||
add_title: Add API Key
|
||||
edit_title: Edit API Key
|
||||
description: Description
|
||||
description_required: Description is required.
|
||||
scope: Scope
|
||||
global: Global
|
||||
read-only: Read-only
|
||||
created_modal:
|
||||
title: API key created
|
||||
api_key: API key
|
||||
description: This key will not be displayed again. Make sure you take a copy before continuing.
|
||||
delete_modal:
|
||||
title: Delete API Key
|
||||
content: Any applications or scripts using this key will no longer be able to access the API. This is permanent!
|
||||
ai_settings:
|
||||
enabled:
|
||||
label: AI enabled
|
||||
check: Enable AI features
|
||||
text: The AI model must be configured correctly before it can be used.
|
||||
provider:
|
||||
label: Provider
|
||||
api_host:
|
||||
label: API host
|
||||
msg: API host is required
|
||||
api_key:
|
||||
label: API key
|
||||
check: Check
|
||||
check_success: "Connection successful."
|
||||
msg: API key is required
|
||||
model:
|
||||
label: Model
|
||||
msg: Model is required
|
||||
add_success: AI settings updated successfully.
|
||||
conversations:
|
||||
topic: Topic
|
||||
helpful: Helpful
|
||||
unhelpful: Unhelpful
|
||||
created: Created
|
||||
action: Action
|
||||
empty: No conversations found.
|
||||
delete_modal:
|
||||
title: Delete conversation
|
||||
content: Are you sure you want to delete this conversation? This is permanent!
|
||||
delete_success: Conversation deleted successfully.
|
||||
mcp:
|
||||
mcp_server:
|
||||
label: MCP server
|
||||
switch: Enabled
|
||||
type:
|
||||
label: Type
|
||||
url:
|
||||
label: URL
|
||||
http_header:
|
||||
label: HTTP header
|
||||
text: Please replace {key} with the API Key.
|
||||
form:
|
||||
optional: (optional)
|
||||
empty: cannot be empty
|
||||
@@ -2347,6 +2437,7 @@ ui:
|
||||
user_normal: This user is already normal.
|
||||
user_suspended: This user has been suspended.
|
||||
user_deleted: This user has been deleted.
|
||||
user_added: User has been added successfully.
|
||||
badge_activated: This badge has been activated.
|
||||
badge_inactivated: This badge has been inactivated.
|
||||
users_deleted: These users have been deleted.
|
||||
|
||||
+97
-6
@@ -110,7 +110,7 @@ backend:
|
||||
rank_invite_someone_to_answer_label:
|
||||
other: Undang seseorang untuk menjawab
|
||||
rank_tag_add_label:
|
||||
other:
|
||||
other: Buat tag baru
|
||||
rank_tag_edit_label:
|
||||
other: Edit tag description (need to review)
|
||||
rank_question_edit_label:
|
||||
@@ -132,7 +132,7 @@ backend:
|
||||
rank_tag_synonym_label:
|
||||
other: Manage tag synonyms
|
||||
email:
|
||||
other:
|
||||
other: Email
|
||||
e_mail:
|
||||
other: Email
|
||||
password:
|
||||
@@ -234,7 +234,7 @@ backend:
|
||||
other: Tidak diizinkan untuk memperbarui.
|
||||
content_cannot_empty:
|
||||
other: Content cannot be empty.
|
||||
content_less_than_minumum:
|
||||
content_less_than_minimum:
|
||||
other: Not enough content entered.
|
||||
rank:
|
||||
fail_to_meet_the_condition:
|
||||
@@ -846,6 +846,16 @@ ui:
|
||||
http_403: HTTP Error 403
|
||||
logout: Log Out
|
||||
posts: Posts
|
||||
ai_assistant: AI Assistant
|
||||
ai_assistant:
|
||||
description: Got a question? Ask it and get answers, perspectives, and recommendations.
|
||||
recent_conversations: Recent Conversations
|
||||
show_more: Show more
|
||||
new: New chat
|
||||
ai_generate: AI-generated from posts and may not be accurate.
|
||||
copy: Copy
|
||||
ask_a_follow_up: Ask a follow-up
|
||||
ask_placeholder: Ask a question
|
||||
notifications:
|
||||
title: Pemberitahuan
|
||||
inbox: Kotak Masuk
|
||||
@@ -1190,8 +1200,7 @@ ui:
|
||||
search:
|
||||
placeholder: Cari
|
||||
footer:
|
||||
build_on: >-
|
||||
Powered by <1> Apache Answer </1>- the open-source software that powers Q&A communities.<br />Made with love © {{cc}}.
|
||||
build_on: Powered by <1> Apache Answer </1>
|
||||
upload_img:
|
||||
name: Ubah
|
||||
loading: sedang memuat...
|
||||
@@ -1768,6 +1777,7 @@ ui:
|
||||
branding: Branding
|
||||
legal: Legal
|
||||
write: Write
|
||||
terms: Terms
|
||||
tos: Terms of Service
|
||||
privacy: Privasi
|
||||
seo: SEO
|
||||
@@ -1778,6 +1788,18 @@ ui:
|
||||
plugins: Plugins
|
||||
installed_plugins: Installed Plugins
|
||||
apperance: Appearance
|
||||
community: Community
|
||||
advanced: Advanced
|
||||
tags: Tags
|
||||
rules: Rules
|
||||
policies: Policies
|
||||
security: Security
|
||||
files: Files
|
||||
apikeys: API Keys
|
||||
intelligence: Intelligence
|
||||
ai_assistant: AI Assistant
|
||||
ai_settings: AI Settings
|
||||
mcp: MCP
|
||||
website_welcome: Welcome to {{site_name}}
|
||||
user_center:
|
||||
login: Login
|
||||
@@ -2089,7 +2111,7 @@ ui:
|
||||
always_display: Always display external content
|
||||
ask_before_display: Ask before displaying external content
|
||||
write:
|
||||
page_title: Write
|
||||
page_title: Files
|
||||
min_content:
|
||||
label: Minimum question body length
|
||||
text: Minimum allowed question body length in characters.
|
||||
@@ -2147,6 +2169,10 @@ ui:
|
||||
primary_color:
|
||||
label: Primary color
|
||||
text: Modify the colors used by your themes
|
||||
layout:
|
||||
label: Layout
|
||||
full_width: Full-width
|
||||
fixed_width: Fixed-width
|
||||
css_and_html:
|
||||
page_title: CSS and HTML
|
||||
custom_css:
|
||||
@@ -2251,6 +2277,70 @@ ui:
|
||||
show_logs: Show logs
|
||||
status: Status
|
||||
title: Badges
|
||||
apikeys:
|
||||
title: API Keys
|
||||
add_api_key: Add API Key
|
||||
desc: Description
|
||||
scope: Scope
|
||||
key: Key
|
||||
created: Created
|
||||
last_used: Last used
|
||||
add_or_edit_modal:
|
||||
add_title: Add API Key
|
||||
edit_title: Edit API Key
|
||||
description: Description
|
||||
description_required: Description is required.
|
||||
scope: Scope
|
||||
global: Global
|
||||
read-only: Read-only
|
||||
created_modal:
|
||||
title: API key created
|
||||
api_key: API key
|
||||
description: This key will not be displayed again. Make sure you take a copy before continuing.
|
||||
delete_modal:
|
||||
title: Delete API Key
|
||||
content: Any applications or scripts using this key will no longer be able to access the API. This is permanent!
|
||||
ai_settings:
|
||||
enabled:
|
||||
label: AI enabled
|
||||
check: Enable AI features
|
||||
text: The AI model must be configured correctly before it can be used.
|
||||
provider:
|
||||
label: Provider
|
||||
api_host:
|
||||
label: API host
|
||||
msg: API host is required
|
||||
api_key:
|
||||
label: API key
|
||||
check: Check
|
||||
check_success: "Connection successful."
|
||||
msg: API key is required
|
||||
model:
|
||||
label: Model
|
||||
msg: Model is required
|
||||
add_success: AI settings updated successfully.
|
||||
conversations:
|
||||
topic: Topic
|
||||
helpful: Helpful
|
||||
unhelpful: Unhelpful
|
||||
created: Created
|
||||
action: Action
|
||||
empty: No conversations found.
|
||||
delete_modal:
|
||||
title: Delete conversation
|
||||
content: Are you sure you want to delete this conversation? This is permanent!
|
||||
delete_success: Conversation deleted successfully.
|
||||
mcp:
|
||||
mcp_server:
|
||||
label: MCP server
|
||||
switch: Enabled
|
||||
type:
|
||||
label: Type
|
||||
url:
|
||||
label: URL
|
||||
http_header:
|
||||
label: HTTP header
|
||||
text: Please replace {key} with the API Key.
|
||||
form:
|
||||
optional: (optional)
|
||||
empty: cannot be empty
|
||||
@@ -2347,6 +2437,7 @@ ui:
|
||||
user_normal: This user is already normal.
|
||||
user_suspended: This user has been suspended.
|
||||
user_deleted: This user has been deleted.
|
||||
user_added: User has been added successfully.
|
||||
badge_activated: This badge has been activated.
|
||||
badge_inactivated: This badge has been inactivated.
|
||||
users_deleted: These users have been deleted.
|
||||
|
||||
+95
-4
@@ -234,7 +234,7 @@ backend:
|
||||
other: Nessun permesso per l'aggiornamento.
|
||||
content_cannot_empty:
|
||||
other: Il contenuto non può essere vuoto.
|
||||
content_less_than_minumum:
|
||||
content_less_than_minimum:
|
||||
other: Not enough content entered.
|
||||
rank:
|
||||
fail_to_meet_the_condition:
|
||||
@@ -846,6 +846,16 @@ ui:
|
||||
http_403: Errore HTTP 403
|
||||
logout: Disconnetti
|
||||
posts: Posts
|
||||
ai_assistant: AI Assistant
|
||||
ai_assistant:
|
||||
description: Got a question? Ask it and get answers, perspectives, and recommendations.
|
||||
recent_conversations: Recent Conversations
|
||||
show_more: Show more
|
||||
new: New chat
|
||||
ai_generate: AI-generated from posts and may not be accurate.
|
||||
copy: Copy
|
||||
ask_a_follow_up: Ask a follow-up
|
||||
ask_placeholder: Ask a question
|
||||
notifications:
|
||||
title: Notifiche
|
||||
inbox: Posta in arrivo
|
||||
@@ -1190,8 +1200,7 @@ ui:
|
||||
search:
|
||||
placeholder: Cerca
|
||||
footer:
|
||||
build_on: >-
|
||||
Basato su <1> Apache Answer </1>, il software open source che alimenta le comunità di domande e risposte.<br />Fatto con amore © {{cc}}.
|
||||
build_on: Powered by <1> Apache Answer </1>
|
||||
upload_img:
|
||||
name: Modifica
|
||||
loading: caricamento in corso...
|
||||
@@ -1768,6 +1777,7 @@ ui:
|
||||
branding: Marchio
|
||||
legal: Legale
|
||||
write: Scrivi
|
||||
terms: Terms
|
||||
tos: Termini del servizio
|
||||
privacy: Privacy
|
||||
seo: SEO
|
||||
@@ -1778,6 +1788,18 @@ ui:
|
||||
plugins: Plugin
|
||||
installed_plugins: Plugin installati
|
||||
apperance: Appearance
|
||||
community: Community
|
||||
advanced: Advanced
|
||||
tags: Tags
|
||||
rules: Rules
|
||||
policies: Policies
|
||||
security: Security
|
||||
files: Files
|
||||
apikeys: API Keys
|
||||
intelligence: Intelligence
|
||||
ai_assistant: AI Assistant
|
||||
ai_settings: AI Settings
|
||||
mcp: MCP
|
||||
website_welcome: Benvenuto/a su {{site_name}}!
|
||||
user_center:
|
||||
login: Accedi
|
||||
@@ -2089,7 +2111,7 @@ ui:
|
||||
always_display: Always display external content
|
||||
ask_before_display: Ask before displaying external content
|
||||
write:
|
||||
page_title: Scrivi
|
||||
page_title: Files
|
||||
min_content:
|
||||
label: Minimum question body length
|
||||
text: Minimum allowed question body length in characters.
|
||||
@@ -2147,6 +2169,10 @@ ui:
|
||||
primary_color:
|
||||
label: Colore primario
|
||||
text: Modifica i colori utilizzati dai tuoi temi
|
||||
layout:
|
||||
label: Layout
|
||||
full_width: Full-width
|
||||
fixed_width: Fixed-width
|
||||
css_and_html:
|
||||
page_title: CSS e HTML
|
||||
custom_css:
|
||||
@@ -2251,6 +2277,70 @@ ui:
|
||||
show_logs: Visualizza i log
|
||||
status: Stato
|
||||
title: Badges
|
||||
apikeys:
|
||||
title: API Keys
|
||||
add_api_key: Add API Key
|
||||
desc: Description
|
||||
scope: Scope
|
||||
key: Key
|
||||
created: Created
|
||||
last_used: Last used
|
||||
add_or_edit_modal:
|
||||
add_title: Add API Key
|
||||
edit_title: Edit API Key
|
||||
description: Description
|
||||
description_required: Description is required.
|
||||
scope: Scope
|
||||
global: Global
|
||||
read-only: Read-only
|
||||
created_modal:
|
||||
title: API key created
|
||||
api_key: API key
|
||||
description: This key will not be displayed again. Make sure you take a copy before continuing.
|
||||
delete_modal:
|
||||
title: Delete API Key
|
||||
content: Any applications or scripts using this key will no longer be able to access the API. This is permanent!
|
||||
ai_settings:
|
||||
enabled:
|
||||
label: AI enabled
|
||||
check: Enable AI features
|
||||
text: The AI model must be configured correctly before it can be used.
|
||||
provider:
|
||||
label: Provider
|
||||
api_host:
|
||||
label: API host
|
||||
msg: API host is required
|
||||
api_key:
|
||||
label: API key
|
||||
check: Check
|
||||
check_success: "Connection successful."
|
||||
msg: API key is required
|
||||
model:
|
||||
label: Model
|
||||
msg: Model is required
|
||||
add_success: AI settings updated successfully.
|
||||
conversations:
|
||||
topic: Topic
|
||||
helpful: Helpful
|
||||
unhelpful: Unhelpful
|
||||
created: Created
|
||||
action: Action
|
||||
empty: No conversations found.
|
||||
delete_modal:
|
||||
title: Delete conversation
|
||||
content: Are you sure you want to delete this conversation? This is permanent!
|
||||
delete_success: Conversation deleted successfully.
|
||||
mcp:
|
||||
mcp_server:
|
||||
label: MCP server
|
||||
switch: Enabled
|
||||
type:
|
||||
label: Type
|
||||
url:
|
||||
label: URL
|
||||
http_header:
|
||||
label: HTTP header
|
||||
text: Please replace {key} with the API Key.
|
||||
form:
|
||||
optional: (opzionale)
|
||||
empty: non può essere vuoto
|
||||
@@ -2347,6 +2437,7 @@ ui:
|
||||
user_normal: Questo utente è già normale.
|
||||
user_suspended: Questo utente è stato sospeso.
|
||||
user_deleted: Questo utente è stato eliminato.
|
||||
user_added: User has been added successfully.
|
||||
badge_activated: Questo badge è stato attivato.
|
||||
badge_inactivated: Questo badge è stato disattivato.
|
||||
users_deleted: These users have been deleted.
|
||||
|
||||
+203
-112
@@ -58,7 +58,7 @@ backend:
|
||||
undelete:
|
||||
other: 復元する
|
||||
merge:
|
||||
other: Merge
|
||||
other: マージ
|
||||
role:
|
||||
name:
|
||||
user:
|
||||
@@ -140,7 +140,7 @@ backend:
|
||||
pass:
|
||||
other: パスワード
|
||||
old_pass:
|
||||
other: Current password
|
||||
other: 現在のパスワード
|
||||
original_text:
|
||||
other: 投稿
|
||||
email_or_password_wrong_error:
|
||||
@@ -173,7 +173,7 @@ backend:
|
||||
question_closed_cannot_add:
|
||||
other: 質問はクローズされて、追加できません。
|
||||
content_cannot_empty:
|
||||
other: Answer content cannot be empty.
|
||||
other: 回答を入力してください。
|
||||
comment:
|
||||
edit_without_permission:
|
||||
other: コメントを編集することはできません。
|
||||
@@ -182,7 +182,7 @@ backend:
|
||||
cannot_edit_after_deadline:
|
||||
other: コメント時間が長すぎて変更できません。
|
||||
content_cannot_empty:
|
||||
other: Comment content cannot be empty.
|
||||
other: コメントを入力してください。
|
||||
email:
|
||||
duplicate:
|
||||
other: メールアドレスは既に存在しています。
|
||||
@@ -233,9 +233,9 @@ backend:
|
||||
cannot_update:
|
||||
other: 更新する権限がありません。
|
||||
content_cannot_empty:
|
||||
other: Content cannot be empty.
|
||||
content_less_than_minumum:
|
||||
other: Not enough content entered.
|
||||
other: 内容を入力してください。
|
||||
content_less_than_minimum:
|
||||
other: 入力された内容の文字数が足りません。
|
||||
rank:
|
||||
fail_to_meet_the_condition:
|
||||
other: 評判ランクが条件を満たしていません
|
||||
@@ -266,7 +266,7 @@ backend:
|
||||
cannot_set_synonym_as_itself:
|
||||
other: 現在のタグの同義語をそのものとして設定することはできません。
|
||||
minimum_count:
|
||||
other: Not enough tags were entered.
|
||||
other: タグが不足しています。
|
||||
smtp:
|
||||
config_from_name_cannot_be_email:
|
||||
other: Fromの名前はメールアドレスにできません。
|
||||
@@ -311,13 +311,13 @@ backend:
|
||||
add_bulk_users_amount_error:
|
||||
other: "一度に追加するユーザーの数は、1 -{{.MaxAmount}} の範囲にする必要があります。"
|
||||
status_suspended_forever:
|
||||
other: "<strong>This user was suspended forever.</strong> This user doesn't meet a community guideline."
|
||||
other: "<strong>このユーザーは永久に停止されました。</strong> このユーザーはコミュニティガイドラインに準拠していません。"
|
||||
status_suspended_until:
|
||||
other: "<strong>This user was suspended until {{.SuspendedUntil}}.</strong> This user doesn't meet a community guideline."
|
||||
other: "<strong>このユーザーは {{.SuspendedUntil}} まで利用停止となりました。</strong> このユーザーはコミュニティ ガイドラインに準拠していません。"
|
||||
status_deleted:
|
||||
other: "This user was deleted."
|
||||
other: "このユーザーは削除されました。"
|
||||
status_inactive:
|
||||
other: "This user is inactive."
|
||||
other: "このユーザーは非アクティブです。"
|
||||
config:
|
||||
read_config_failed:
|
||||
other: configの読み込みに失敗しました
|
||||
@@ -506,7 +506,7 @@ backend:
|
||||
title:
|
||||
other: "[{{.SiteName}}] 新しい質問: {{.QuestionTitle}}"
|
||||
body:
|
||||
other: "<a href='{{.QuestionUrl}}'>{{.QuestionTitle}}</a><br>\n<small>{{.Tags}}</small><br><br>\n\n--<br>\nNote: This is an automatic system email, please do not reply to this message as your response will not be seen.<br><br>\n\n<small><a href='{{.UnsubscribeUrl}}'>Unsubscribe</a></small>"
|
||||
other: "<a href='{{.QuestionUrl}}'>{{.QuestionTitle}}</a><br>\n<small>{{.Tags}}</small><br><br>\n\n--<br>\n注: これはシステムから送信される自動メールです。ご返信いただいても返信は表示されませんので、ご返信はご遠慮ください。<br><br>\n\n<small><a href='{{.UnsubscribeUrl}}'>購読解除</a></small>"
|
||||
pass_reset:
|
||||
title:
|
||||
other: "[{{.SiteName }}] パスワードリセット"
|
||||
@@ -578,7 +578,7 @@ backend:
|
||||
name:
|
||||
other: はじめてのリンク
|
||||
desc:
|
||||
other: First added a link to another post.
|
||||
other: 初めて別の投稿へのリンクを追加した。
|
||||
first_reaction:
|
||||
name:
|
||||
other: 初めてのリアクション
|
||||
@@ -598,17 +598,17 @@ backend:
|
||||
name:
|
||||
other: コメントマン
|
||||
desc:
|
||||
other: 5つコメントをした
|
||||
other: 5つのコメントをした
|
||||
new_user_of_the_month:
|
||||
name:
|
||||
other: 今月の新しいユーザー
|
||||
desc:
|
||||
other: 最初の月に優れた貢献
|
||||
other: 最初の1ヶ月で優れた貢献をした
|
||||
read_guidelines:
|
||||
name:
|
||||
other: ガイドラインを読んだ
|
||||
desc:
|
||||
other: '「コミュニティガイドライン」をご覧ください。'
|
||||
other: '「コミュニティガイドライン」を読んだ'
|
||||
reader:
|
||||
name:
|
||||
other: リーダー
|
||||
@@ -748,7 +748,7 @@ backend:
|
||||
name:
|
||||
other: 素晴らしい回答
|
||||
desc:
|
||||
other: 回答スコアは50以上!!!!1
|
||||
other: 回答スコアは50以上!!!!
|
||||
nice_question:
|
||||
name:
|
||||
other: ナイスな質問
|
||||
@@ -820,7 +820,7 @@ ui:
|
||||
tag_wiki: タグ wiki
|
||||
create_tag: タグを作成
|
||||
edit_tag: タグを編集
|
||||
ask_a_question: Create Question
|
||||
ask_a_question: 質問を作成
|
||||
edit_question: 質問を編集
|
||||
edit_answer: 回答を編集
|
||||
search: 検索
|
||||
@@ -844,7 +844,17 @@ ui:
|
||||
http_50X: HTTP エラー 500
|
||||
http_403: HTTP エラー 403
|
||||
logout: ログアウト
|
||||
posts: Posts
|
||||
posts: 投稿
|
||||
ai_assistant: AI Assistant
|
||||
ai_assistant:
|
||||
description: Got a question? Ask it and get answers, perspectives, and recommendations.
|
||||
recent_conversations: Recent Conversations
|
||||
show_more: Show more
|
||||
new: New chat
|
||||
ai_generate: AI-generated from posts and may not be accurate.
|
||||
copy: Copy
|
||||
ask_a_follow_up: Ask a follow-up
|
||||
ask_placeholder: Ask a question
|
||||
notifications:
|
||||
title: 通知
|
||||
inbox: 受信トレイ
|
||||
@@ -1042,14 +1052,14 @@ ui:
|
||||
tip: 本当に削除してもよろしいですか?
|
||||
close: クローズ
|
||||
merge:
|
||||
title: Merge tag
|
||||
source_tag_title: Source tag
|
||||
source_tag_description: The source tag and its associated data will be remapped to the target tag.
|
||||
target_tag_title: Target tag
|
||||
target_tag_description: A synonym between these two tags will be created after merging.
|
||||
no_results: No tags matched
|
||||
btn_submit: Submit
|
||||
btn_close: Close
|
||||
title: タグをマージ
|
||||
source_tag_title: ソース タグ
|
||||
source_tag_description: ソースタグとそれに関連付けられたデータは、ターゲットタグに再マップされます。
|
||||
target_tag_title: ターゲットタグ
|
||||
target_tag_description: マージ後、これら2つのタグの同義語が作成されます。
|
||||
no_results: 一致するタグはありません
|
||||
btn_submit: 送信
|
||||
btn_close: 閉じる
|
||||
edit_tag:
|
||||
title: タグを編集
|
||||
default_reason: タグを編集
|
||||
@@ -1068,9 +1078,9 @@ ui:
|
||||
day: 日
|
||||
hours: 時
|
||||
days: 日
|
||||
month: month
|
||||
months: months
|
||||
year: year
|
||||
month: 月
|
||||
months: ヶ月
|
||||
year: 年
|
||||
reaction:
|
||||
heart: ハート
|
||||
smile: 笑顔
|
||||
@@ -1126,10 +1136,10 @@ ui:
|
||||
more: もっと見る
|
||||
wiki: Wiki
|
||||
ask:
|
||||
title: Create Question
|
||||
title: 質問を作成
|
||||
edit_title: 質問を編集
|
||||
default_reason: 質問を編集
|
||||
default_first_reason: Create question
|
||||
default_first_reason: 質問を作成
|
||||
similar_questions: 類似の質問
|
||||
form:
|
||||
fields:
|
||||
@@ -1137,7 +1147,7 @@ ui:
|
||||
label: 修正
|
||||
title:
|
||||
label: タイトル
|
||||
placeholder: What's your topic? Be specific.
|
||||
placeholder: どのようなトピックですか?具体的に教えてください。
|
||||
msg:
|
||||
empty: タイトルを空にすることはできません。
|
||||
range: タイトルは最大150文字までです
|
||||
@@ -1146,8 +1156,8 @@ ui:
|
||||
msg:
|
||||
empty: 本文を空にすることはできません。
|
||||
hint:
|
||||
optional_body: Describe what the question is about.
|
||||
minimum_characters: "Describe what the question is about, at least {{min_content_length}} characters are required."
|
||||
optional_body: 質問を記載してください。
|
||||
minimum_characters: "質問を記載してください。{{min_content_length}} 文字以上の記載が必要です。"
|
||||
tags:
|
||||
label: タグ
|
||||
msg:
|
||||
@@ -1168,9 +1178,9 @@ ui:
|
||||
add_btn: タグを追加
|
||||
create_btn: 新しタグを作成
|
||||
search_tag: タグを検索
|
||||
hint: Describe what your content is about, at least one tag is required.
|
||||
hint_zero_tags: Describe what your content is about.
|
||||
hint_more_than_one_tag: "Describe what your content is about, at least {{min_tags_number}} tags are required."
|
||||
hint: 内容を記載してください。1つ以上のタグが必要です。
|
||||
hint_zero_tags: 内容を記載してください。
|
||||
hint_more_than_one_tag: "内容を記載してください。{{min_content_length}} 文字以上の記載が必要です。"
|
||||
no_result: 一致するタグはありません
|
||||
tag_required_text: 必須タグ (少なくとも 1 つ)
|
||||
header:
|
||||
@@ -1189,8 +1199,7 @@ ui:
|
||||
search:
|
||||
placeholder: 検索
|
||||
footer:
|
||||
build_on: >-
|
||||
Powered by <1> Apache Answer </1>- the open-source software that powers Q&A communities.<br />Made with love © {{cc}}.
|
||||
build_on: Powered by <1> Apache Answer </1>
|
||||
upload_img:
|
||||
name: 変更
|
||||
loading: 読み込み中…
|
||||
@@ -1223,7 +1232,7 @@ ui:
|
||||
msg:
|
||||
empty: 名前を空にすることはできません。
|
||||
range: 2~30文字の名前を設定してください。
|
||||
character: 'Must use the character set "a-z", "0-9", " - . _"'
|
||||
character: '使用可能な文字は、英小字「a-z」、数字「0-9」、記号「- . _ 」のみです'
|
||||
email:
|
||||
label: メールアドレス
|
||||
msg:
|
||||
@@ -1295,13 +1304,13 @@ ui:
|
||||
display_name:
|
||||
label: 表示名
|
||||
msg: 表示名は必須です。
|
||||
msg_range: Display name must be 2-30 characters in length.
|
||||
msg_range: 表示名は 2 ~ 30 文字で入力してください。
|
||||
username:
|
||||
label: ユーザー名
|
||||
caption: ユーザーは "@username" としてあなたをメンションできます。
|
||||
msg: ユーザー名は空にできません。
|
||||
msg_range: Username must be 2-30 characters in length.
|
||||
character: 'Must use the character set "a-z", "0-9", "- . _"'
|
||||
msg_range: ユーザー名は2 ~ 30文字で入力してください。
|
||||
character: '使用可能な文字は、英小字「a-z」、数字「0-9」、記号「- . _ 」のみです'
|
||||
avatar:
|
||||
label: プロフィール画像
|
||||
gravatar: Gravatar
|
||||
@@ -1355,7 +1364,7 @@ ui:
|
||||
new_pass:
|
||||
label: 新しいパスワード
|
||||
pass_confirm:
|
||||
label: 新しいパスワードの確認
|
||||
label: 新しいパスワードの確認
|
||||
interface:
|
||||
heading: 外観
|
||||
lang:
|
||||
@@ -1376,25 +1385,25 @@ ui:
|
||||
review: レビュー後にあなたのリビジョンが表示されます。
|
||||
sent_success: 正常に送信されました。
|
||||
related_question:
|
||||
title: Related
|
||||
title: 関連
|
||||
answers: 回答
|
||||
linked_question:
|
||||
title: Linked
|
||||
description: Posts linked to
|
||||
no_linked_question: No contents linked from this content.
|
||||
title: リンク済
|
||||
description: リンクされた投稿
|
||||
no_linked_question: このコンテンツからリンクされたコンテンツはありません。
|
||||
invite_to_answer:
|
||||
title: People Asked
|
||||
desc: Select people who you think might know the answer.
|
||||
title: 回答をリクエスト
|
||||
desc: 答えられそうな人を招待します。
|
||||
invite: 回答に招待する
|
||||
add: ユーザーを追加
|
||||
search: ユーザーを検索
|
||||
question_detail:
|
||||
action: 動作
|
||||
created: Created
|
||||
created: 作成済
|
||||
Asked: 質問済み
|
||||
asked: 質問済み
|
||||
update: 修正済み
|
||||
Edited: Edited
|
||||
Edited: 編集済
|
||||
edit: 編集済み
|
||||
commented: コメントしました
|
||||
Views: 閲覧回数
|
||||
@@ -1476,7 +1485,7 @@ ui:
|
||||
signup: 新規登録
|
||||
logout: ログアウト
|
||||
verify: 認証
|
||||
create: Create
|
||||
create: 作成
|
||||
approve: 承認
|
||||
reject: 却下
|
||||
skip: スキップする
|
||||
@@ -1508,16 +1517,16 @@ ui:
|
||||
normal: 通常
|
||||
closed: クローズ済み
|
||||
deleted: 削除済み
|
||||
deleted_permanently: Deleted permanently
|
||||
deleted_permanently: 完全に削除する
|
||||
pending: 処理待ち
|
||||
more: もっと見る
|
||||
view: View
|
||||
card: Card
|
||||
compact: Compact
|
||||
display_below: Display below
|
||||
always_display: Always display
|
||||
or: or
|
||||
back_sites: Back to sites
|
||||
view: 表示方法
|
||||
card: カード
|
||||
compact: コンパクト
|
||||
display_below: 以下に表示
|
||||
always_display: 常に表示
|
||||
or: または
|
||||
back_sites: サイトに戻る
|
||||
search:
|
||||
title: 検索結果
|
||||
keywords: キーワード
|
||||
@@ -1525,7 +1534,7 @@ ui:
|
||||
follow: フォロー
|
||||
following: フォロー中
|
||||
counts: "結果:{{count}}"
|
||||
counts_loading: "... Results"
|
||||
counts_loading: "... 結果"
|
||||
more: もっと見る
|
||||
sort_btns:
|
||||
relevance: 関連性
|
||||
@@ -1548,13 +1557,13 @@ ui:
|
||||
via: 投稿を共有...
|
||||
copied: コピーしました
|
||||
facebook: Facebookで共有
|
||||
twitter: Share to X
|
||||
twitter: Xでシェア
|
||||
cannot_vote_for_self: 自分の投稿には投票できません。
|
||||
modal_confirm:
|
||||
title: エラー...
|
||||
delete_permanently:
|
||||
title: Delete permanently
|
||||
content: Are you sure you want to delete permanently?
|
||||
title: 完全に削除する
|
||||
content: 完全に削除しても良いですか?
|
||||
account_result:
|
||||
success: 新しいアカウントが確認されました。ホームページにリダイレクトされます。
|
||||
link: ホームページへ
|
||||
@@ -1577,13 +1586,13 @@ ui:
|
||||
all_questions: すべての質問
|
||||
x_questions: "{{ count }} の質問"
|
||||
x_answers: "{{ count }} の回答"
|
||||
x_posts: "{{ count }} Posts"
|
||||
x_posts: "{{ count }} の回答"
|
||||
questions: 質問
|
||||
answers: 回答
|
||||
newest: 最新
|
||||
active: 有効
|
||||
hot: 人気
|
||||
frequent: Frequent
|
||||
frequent: 関心順
|
||||
recommend: おすすめ
|
||||
score: スコア
|
||||
unanswered: 未回答
|
||||
@@ -1660,22 +1669,22 @@ ui:
|
||||
placeholder: /data/answer.db
|
||||
msg: データベースファイルは空にできません。
|
||||
ssl_enabled:
|
||||
label: Enable SSL
|
||||
label: SSLを有効化
|
||||
ssl_enabled_on:
|
||||
label: On
|
||||
ssl_enabled_off:
|
||||
label: Off
|
||||
ssl_mode:
|
||||
label: SSL Mode
|
||||
label: SSL モード
|
||||
ssl_root_cert:
|
||||
placeholder: sslrootcert file path
|
||||
msg: Path to sslrootcert file cannot be empty
|
||||
placeholder: sslrootcert ファイルパス
|
||||
msg: sslrootcert ファイルパスは空にできません
|
||||
ssl_cert:
|
||||
placeholder: sslcert file path
|
||||
msg: Path to sslcert file cannot be empty
|
||||
placeholder: sslcert ファイルパス
|
||||
msg: sslcert ファイルパスは空にできません
|
||||
ssl_key:
|
||||
placeholder: sslkey file path
|
||||
msg: Path to sslkey file cannot be empty
|
||||
placeholder: sslkey ファイルパス
|
||||
msg: sslkey ファイルパスは空にできません
|
||||
config_yaml:
|
||||
title: config.yamlを作成
|
||||
label: config.yaml ファイルが作成されました。
|
||||
@@ -1708,8 +1717,8 @@ ui:
|
||||
admin_name:
|
||||
label: 名前
|
||||
msg: 名前を空にすることはできません。
|
||||
character: 'Must use the character set "a-z", "0-9", " - . _"'
|
||||
msg_max_length: Name must be between 2 to 30 characters in length.
|
||||
character: '使用可能な文字は、英小字「a-z」、数字「0-9」、記号「- . _ 」のみです'
|
||||
msg_max_length: 名前は2 ~ 30文字で入力してください。
|
||||
admin_password:
|
||||
label: パスワード
|
||||
text: >-
|
||||
@@ -1718,9 +1727,9 @@ ui:
|
||||
msg_min_length: パスワードは8文字以上でなければなりません。
|
||||
msg_max_length: パスワードは最大 32 文字でなければなりません。
|
||||
admin_confirm_password:
|
||||
label: "Confirm Password"
|
||||
text: "Please re-enter your password to confirm."
|
||||
msg: "Confirm password does not match."
|
||||
label: "パスワードの確認"
|
||||
text: "確認のため、パスワードを再入力してください。"
|
||||
msg: "確認用パスワードが一致しません"
|
||||
admin_email:
|
||||
label: メールアドレス
|
||||
text: ログインするにはこのメールアドレスが必要です。
|
||||
@@ -1768,7 +1777,8 @@ ui:
|
||||
smtp: SMTP
|
||||
branding: ブランディング
|
||||
legal: 法的事項
|
||||
write: 書き
|
||||
write: 編集
|
||||
terms: 規約
|
||||
tos: 利用規約
|
||||
privacy: プライバシー
|
||||
seo: SEO
|
||||
@@ -1778,7 +1788,19 @@ ui:
|
||||
privileges: 特典
|
||||
plugins: プラグイン
|
||||
installed_plugins: 使用中のプラグイン
|
||||
apperance: Appearance
|
||||
apperance: 外観
|
||||
community: Community
|
||||
advanced: Advanced
|
||||
tags: Tags
|
||||
rules: Rules
|
||||
policies: Policies
|
||||
security: Security
|
||||
files: Files
|
||||
apikeys: API Keys
|
||||
intelligence: Intelligence
|
||||
ai_assistant: AI Assistant
|
||||
ai_settings: AI Settings
|
||||
mcp: MCP
|
||||
website_welcome: '{{site_name}} へようこそ'
|
||||
user_center:
|
||||
login: ログイン
|
||||
@@ -1878,10 +1900,10 @@ ui:
|
||||
fields:
|
||||
display_name:
|
||||
label: 表示名
|
||||
msg_range: Display name must be 2-30 characters in length.
|
||||
msg_range: 表示名は 2 ~ 30 文字で入力してください。
|
||||
username:
|
||||
label: ユーザー名
|
||||
msg_range: Username must be 2-30 characters in length.
|
||||
msg_range: ユーザー名は 2 ~ 30 文字で入力してください。
|
||||
email:
|
||||
label: メールアドレス
|
||||
msg_invalid: 無効なメールアドレス
|
||||
@@ -1913,10 +1935,10 @@ ui:
|
||||
name: 名前
|
||||
email: メールアドレス
|
||||
reputation: 評価
|
||||
created_at: Created time
|
||||
delete_at: Deleted time
|
||||
suspend_at: Suspended time
|
||||
suspend_until: Suspend until
|
||||
created_at: 作成日時
|
||||
delete_at: 削除日時
|
||||
suspend_at: 凍結時間
|
||||
suspend_until: まで凍結
|
||||
status: ステータス
|
||||
role: ロール
|
||||
action: 操作
|
||||
@@ -1951,8 +1973,8 @@ ui:
|
||||
suspend_user:
|
||||
title: ユーザーをサスペンドにする
|
||||
content: 一時停止中のユーザーはログインできません。
|
||||
label: How long will the user be suspended for?
|
||||
forever: Forever
|
||||
label: いつまで凍結しますか?
|
||||
forever: 無期限
|
||||
questions:
|
||||
page_title: 質問
|
||||
unlisted: 限定公開済み
|
||||
@@ -2014,11 +2036,11 @@ ui:
|
||||
msg: タイムゾーンを空にすることはできません。
|
||||
text: あなたのタイムゾーンを選択してください。
|
||||
avatar:
|
||||
label: Default avatar
|
||||
text: For users without a custom avatar of their own.
|
||||
label: デフォルトのアバター
|
||||
text: 独自のカスタムアバターを持たないユーザー向け。
|
||||
gravatar_base_url:
|
||||
label: Gravatar base URL
|
||||
text: URL of the Gravatar provider's API base. Ignored when empty.
|
||||
label: GravatarのベースURL
|
||||
text: GravatarプロバイダーのAPIベースのURL。空の場合は無視されます。
|
||||
smtp:
|
||||
page_title: SMTP
|
||||
from_email:
|
||||
@@ -2085,22 +2107,22 @@ ui:
|
||||
label: プライバシーポリシー
|
||||
text: "ここにプライバシーポリシーの内容を追加できます。すでに他の場所でホストされているドキュメントを持っている場合は、こちらにフルURLを入力してください。"
|
||||
external_content_display:
|
||||
label: External content
|
||||
text: "Content includes images, videos, and media embedded from external websites."
|
||||
always_display: Always display external content
|
||||
ask_before_display: Ask before displaying external content
|
||||
label: 外部コンテンツ
|
||||
text: "コンテンツには、外部ウェブサイトから埋め込まれた画像、ビデオ、およびメディアが含まれます"
|
||||
always_display: 常に外部コンテンツを表示する
|
||||
ask_before_display: 外部コンテンツを表示する前に確認する
|
||||
write:
|
||||
page_title: 編集
|
||||
page_title: Files
|
||||
min_content:
|
||||
label: Minimum question body length
|
||||
text: Minimum allowed question body length in characters.
|
||||
label: 質問に必要な文字数
|
||||
text: 質問の投稿に必要な本文の文字数です。
|
||||
restrict_answer:
|
||||
title: 回答を書く
|
||||
label: 各ユーザーは同じ質問に対して1つの回答しか書けません
|
||||
text: "ユーザが同じ質問に複数の回答を書き込めるようにするにはオフにします。これにより回答がフォーカスされていない可能性があります。"
|
||||
min_tags:
|
||||
label: "Minimum tags per question"
|
||||
text: "Minimum number of tags required in a question."
|
||||
label: "質問に必要なタグ数"
|
||||
text: "質問の投稿に必要なタグの数です。"
|
||||
recommend_tags:
|
||||
label: おすすめタグ
|
||||
text: "デフォルトでドロップダウンリストに推奨タグが表示されます。"
|
||||
@@ -2144,10 +2166,14 @@ ui:
|
||||
color_scheme:
|
||||
label: 配色
|
||||
navbar_style:
|
||||
label: Navbar background style
|
||||
label: ナビゲーションバーの背景スタイル
|
||||
primary_color:
|
||||
label: メインカラー
|
||||
text: テーマで使用される色を変更する
|
||||
layout:
|
||||
label: Layout
|
||||
full_width: Full-width
|
||||
fixed_width: Fixed-width
|
||||
css_and_html:
|
||||
page_title: CSS と HTML
|
||||
custom_css:
|
||||
@@ -2252,6 +2278,70 @@ ui:
|
||||
show_logs: ログを表示
|
||||
status: ステータス
|
||||
title: バッジ
|
||||
apikeys:
|
||||
title: API Keys
|
||||
add_api_key: Add API Key
|
||||
desc: Description
|
||||
scope: Scope
|
||||
key: Key
|
||||
created: Created
|
||||
last_used: Last used
|
||||
add_or_edit_modal:
|
||||
add_title: Add API Key
|
||||
edit_title: Edit API Key
|
||||
description: Description
|
||||
description_required: Description is required.
|
||||
scope: Scope
|
||||
global: Global
|
||||
read-only: Read-only
|
||||
created_modal:
|
||||
title: API key created
|
||||
api_key: API key
|
||||
description: This key will not be displayed again. Make sure you take a copy before continuing.
|
||||
delete_modal:
|
||||
title: Delete API Key
|
||||
content: Any applications or scripts using this key will no longer be able to access the API. This is permanent!
|
||||
ai_settings:
|
||||
enabled:
|
||||
label: AI enabled
|
||||
check: Enable AI features
|
||||
text: The AI model must be configured correctly before it can be used.
|
||||
provider:
|
||||
label: Provider
|
||||
api_host:
|
||||
label: API host
|
||||
msg: API host is required
|
||||
api_key:
|
||||
label: API key
|
||||
check: Check
|
||||
check_success: "Connection successful."
|
||||
msg: API key is required
|
||||
model:
|
||||
label: Model
|
||||
msg: Model is required
|
||||
add_success: AI settings updated successfully.
|
||||
conversations:
|
||||
topic: Topic
|
||||
helpful: Helpful
|
||||
unhelpful: Unhelpful
|
||||
created: Created
|
||||
action: Action
|
||||
empty: No conversations found.
|
||||
delete_modal:
|
||||
title: Delete conversation
|
||||
content: Are you sure you want to delete this conversation? This is permanent!
|
||||
delete_success: Conversation deleted successfully.
|
||||
mcp:
|
||||
mcp_server:
|
||||
label: MCP server
|
||||
switch: Enabled
|
||||
type:
|
||||
label: Type
|
||||
url:
|
||||
label: URL
|
||||
http_header:
|
||||
label: HTTP header
|
||||
text: Please replace {key} with the API Key.
|
||||
form:
|
||||
optional: (任意)
|
||||
empty: 空にすることはできません
|
||||
@@ -2348,13 +2438,14 @@ ui:
|
||||
user_normal: このユーザーは既に有効です。
|
||||
user_suspended: このユーザーは凍結されています。
|
||||
user_deleted: このユーザーは削除されました。
|
||||
user_added: User has been added successfully.
|
||||
badge_activated: このバッジは有効化されました。
|
||||
badge_inactivated: このバッジは無効化されています。
|
||||
users_deleted: These users have been deleted.
|
||||
posts_deleted: These questions have been deleted.
|
||||
answers_deleted: These answers have been deleted.
|
||||
copy: Copy to clipboard
|
||||
copied: Copied
|
||||
external_content_warning: External images/media are not displayed.
|
||||
users_deleted: このユーザーは削除されました。
|
||||
posts_deleted: この質問は削除されています。
|
||||
answers_deleted: この回答は削除されています。
|
||||
copy: クリップボードにコピー
|
||||
copied: コピーしました
|
||||
external_content_warning: 外部の画像/メディアは表示されません。
|
||||
|
||||
|
||||
|
||||
+95
-4
@@ -234,7 +234,7 @@ backend:
|
||||
other: 업데이트 권한이 없습니다.
|
||||
content_cannot_empty:
|
||||
other: 내용은 비워둘 수 없습니다.
|
||||
content_less_than_minumum:
|
||||
content_less_than_minimum:
|
||||
other: Not enough content entered.
|
||||
rank:
|
||||
fail_to_meet_the_condition:
|
||||
@@ -846,6 +846,16 @@ ui:
|
||||
http_403: HTTP 오류 403
|
||||
logout: 로그아웃
|
||||
posts: Posts
|
||||
ai_assistant: AI Assistant
|
||||
ai_assistant:
|
||||
description: Got a question? Ask it and get answers, perspectives, and recommendations.
|
||||
recent_conversations: Recent Conversations
|
||||
show_more: Show more
|
||||
new: New chat
|
||||
ai_generate: AI-generated from posts and may not be accurate.
|
||||
copy: Copy
|
||||
ask_a_follow_up: Ask a follow-up
|
||||
ask_placeholder: Ask a question
|
||||
notifications:
|
||||
title: 알림
|
||||
inbox: 받은 편지함
|
||||
@@ -1190,8 +1200,7 @@ ui:
|
||||
search:
|
||||
placeholder: 검색
|
||||
footer:
|
||||
build_on: >-
|
||||
Powered by <1> Apache Answer </1> - Q&A 커뮤니티를 지원하는 오픈 소스 소프트웨어입니다.<br />Made with love © {{cc}}.
|
||||
build_on: Powered by <1> Apache Answer </1>
|
||||
upload_img:
|
||||
name: 변경
|
||||
loading: 로딩 중...
|
||||
@@ -1768,6 +1777,7 @@ ui:
|
||||
branding: 브랜딩
|
||||
legal: 법적 사항
|
||||
write: 글 작성
|
||||
terms: Terms
|
||||
tos: 이용 약관
|
||||
privacy: 개인정보 보호
|
||||
seo: 검색 엔진 최적화
|
||||
@@ -1778,6 +1788,18 @@ ui:
|
||||
plugins: 플러그인
|
||||
installed_plugins: 설치된 플러그인
|
||||
apperance: 모양
|
||||
community: Community
|
||||
advanced: Advanced
|
||||
tags: Tags
|
||||
rules: Rules
|
||||
policies: Policies
|
||||
security: Security
|
||||
files: Files
|
||||
apikeys: API Keys
|
||||
intelligence: Intelligence
|
||||
ai_assistant: AI Assistant
|
||||
ai_settings: AI Settings
|
||||
mcp: MCP
|
||||
website_welcome: '{{site_name}}에 오신 것을 환영합니다'
|
||||
user_center:
|
||||
login: 로그인
|
||||
@@ -2089,7 +2111,7 @@ ui:
|
||||
always_display: 항상 외부 콘텐츠 표시
|
||||
ask_before_display: 외부 콘텐츠 표시 전 확인
|
||||
write:
|
||||
page_title: 작성
|
||||
page_title: Files
|
||||
min_content:
|
||||
label: Minimum question body length
|
||||
text: Minimum allowed question body length in characters.
|
||||
@@ -2147,6 +2169,10 @@ ui:
|
||||
primary_color:
|
||||
label: 주요 색상
|
||||
text: 테마에서 사용할 색상을 수정합니다.
|
||||
layout:
|
||||
label: Layout
|
||||
full_width: Full-width
|
||||
fixed_width: Fixed-width
|
||||
css_and_html:
|
||||
page_title: CSS 및 HTML
|
||||
custom_css:
|
||||
@@ -2251,6 +2277,70 @@ ui:
|
||||
show_logs: 로그 표시
|
||||
status: 상태
|
||||
title: 뱃지
|
||||
apikeys:
|
||||
title: API Keys
|
||||
add_api_key: Add API Key
|
||||
desc: Description
|
||||
scope: Scope
|
||||
key: Key
|
||||
created: Created
|
||||
last_used: Last used
|
||||
add_or_edit_modal:
|
||||
add_title: Add API Key
|
||||
edit_title: Edit API Key
|
||||
description: Description
|
||||
description_required: Description is required.
|
||||
scope: Scope
|
||||
global: Global
|
||||
read-only: Read-only
|
||||
created_modal:
|
||||
title: API key created
|
||||
api_key: API key
|
||||
description: This key will not be displayed again. Make sure you take a copy before continuing.
|
||||
delete_modal:
|
||||
title: Delete API Key
|
||||
content: Any applications or scripts using this key will no longer be able to access the API. This is permanent!
|
||||
ai_settings:
|
||||
enabled:
|
||||
label: AI enabled
|
||||
check: Enable AI features
|
||||
text: The AI model must be configured correctly before it can be used.
|
||||
provider:
|
||||
label: Provider
|
||||
api_host:
|
||||
label: API host
|
||||
msg: API host is required
|
||||
api_key:
|
||||
label: API key
|
||||
check: Check
|
||||
check_success: "Connection successful."
|
||||
msg: API key is required
|
||||
model:
|
||||
label: Model
|
||||
msg: Model is required
|
||||
add_success: AI settings updated successfully.
|
||||
conversations:
|
||||
topic: Topic
|
||||
helpful: Helpful
|
||||
unhelpful: Unhelpful
|
||||
created: Created
|
||||
action: Action
|
||||
empty: No conversations found.
|
||||
delete_modal:
|
||||
title: Delete conversation
|
||||
content: Are you sure you want to delete this conversation? This is permanent!
|
||||
delete_success: Conversation deleted successfully.
|
||||
mcp:
|
||||
mcp_server:
|
||||
label: MCP server
|
||||
switch: Enabled
|
||||
type:
|
||||
label: Type
|
||||
url:
|
||||
label: URL
|
||||
http_header:
|
||||
label: HTTP header
|
||||
text: Please replace {key} with the API Key.
|
||||
form:
|
||||
optional: (선택 사항)
|
||||
empty: 비어 있을 수 없습니다
|
||||
@@ -2347,6 +2437,7 @@ ui:
|
||||
user_normal: 이 사용자는 이미 일반 사용자입니다.
|
||||
user_suspended: 이 사용자가 정지되었습니다.
|
||||
user_deleted: 이 사용자가 삭제되었습니다.
|
||||
user_added: User has been added successfully.
|
||||
badge_activated: 이 배지가 활성화되었습니다.
|
||||
badge_inactivated: 이 배지가 비활성화되었습니다.
|
||||
users_deleted: 이 사용자들이 삭제되었습니다.
|
||||
|
||||
+95
-4
@@ -234,7 +234,7 @@ backend:
|
||||
other: No permission to update.
|
||||
content_cannot_empty:
|
||||
other: Content cannot be empty.
|
||||
content_less_than_minumum:
|
||||
content_less_than_minimum:
|
||||
other: Not enough content entered.
|
||||
rank:
|
||||
fail_to_meet_the_condition:
|
||||
@@ -846,6 +846,16 @@ ui:
|
||||
http_403: HTTP Error 403
|
||||
logout: Log Out
|
||||
posts: Posts
|
||||
ai_assistant: AI Assistant
|
||||
ai_assistant:
|
||||
description: Got a question? Ask it and get answers, perspectives, and recommendations.
|
||||
recent_conversations: Recent Conversations
|
||||
show_more: Show more
|
||||
new: New chat
|
||||
ai_generate: AI-generated from posts and may not be accurate.
|
||||
copy: Copy
|
||||
ask_a_follow_up: Ask a follow-up
|
||||
ask_placeholder: Ask a question
|
||||
notifications:
|
||||
title: Notifications
|
||||
inbox: Inbox
|
||||
@@ -1190,8 +1200,7 @@ ui:
|
||||
search:
|
||||
placeholder: Search
|
||||
footer:
|
||||
build_on: >-
|
||||
Powered by <1> Apache Answer </1>- the open-source software that powers Q&A communities.<br />Made with love © {{cc}}.
|
||||
build_on: Powered by <1> Apache Answer </1>
|
||||
upload_img:
|
||||
name: Change
|
||||
loading: loading...
|
||||
@@ -1768,6 +1777,7 @@ ui:
|
||||
branding: Branding
|
||||
legal: Legal
|
||||
write: Write
|
||||
terms: Terms
|
||||
tos: Terms of Service
|
||||
privacy: Privacy
|
||||
seo: SEO
|
||||
@@ -1778,6 +1788,18 @@ ui:
|
||||
plugins: Plugins
|
||||
installed_plugins: Installed Plugins
|
||||
apperance: Appearance
|
||||
community: Community
|
||||
advanced: Advanced
|
||||
tags: Tags
|
||||
rules: Rules
|
||||
policies: Policies
|
||||
security: Security
|
||||
files: Files
|
||||
apikeys: API Keys
|
||||
intelligence: Intelligence
|
||||
ai_assistant: AI Assistant
|
||||
ai_settings: AI Settings
|
||||
mcp: MCP
|
||||
website_welcome: Welcome to {{site_name}}
|
||||
user_center:
|
||||
login: Login
|
||||
@@ -2089,7 +2111,7 @@ ui:
|
||||
always_display: Always display external content
|
||||
ask_before_display: Ask before displaying external content
|
||||
write:
|
||||
page_title: Write
|
||||
page_title: Files
|
||||
min_content:
|
||||
label: Minimum question body length
|
||||
text: Minimum allowed question body length in characters.
|
||||
@@ -2147,6 +2169,10 @@ ui:
|
||||
primary_color:
|
||||
label: Primary color
|
||||
text: Modify the colors used by your themes
|
||||
layout:
|
||||
label: Layout
|
||||
full_width: Full-width
|
||||
fixed_width: Fixed-width
|
||||
css_and_html:
|
||||
page_title: CSS and HTML
|
||||
custom_css:
|
||||
@@ -2251,6 +2277,70 @@ ui:
|
||||
show_logs: Show logs
|
||||
status: Status
|
||||
title: Badges
|
||||
apikeys:
|
||||
title: API Keys
|
||||
add_api_key: Add API Key
|
||||
desc: Description
|
||||
scope: Scope
|
||||
key: Key
|
||||
created: Created
|
||||
last_used: Last used
|
||||
add_or_edit_modal:
|
||||
add_title: Add API Key
|
||||
edit_title: Edit API Key
|
||||
description: Description
|
||||
description_required: Description is required.
|
||||
scope: Scope
|
||||
global: Global
|
||||
read-only: Read-only
|
||||
created_modal:
|
||||
title: API key created
|
||||
api_key: API key
|
||||
description: This key will not be displayed again. Make sure you take a copy before continuing.
|
||||
delete_modal:
|
||||
title: Delete API Key
|
||||
content: Any applications or scripts using this key will no longer be able to access the API. This is permanent!
|
||||
ai_settings:
|
||||
enabled:
|
||||
label: AI enabled
|
||||
check: Enable AI features
|
||||
text: The AI model must be configured correctly before it can be used.
|
||||
provider:
|
||||
label: Provider
|
||||
api_host:
|
||||
label: API host
|
||||
msg: API host is required
|
||||
api_key:
|
||||
label: API key
|
||||
check: Check
|
||||
check_success: "Connection successful."
|
||||
msg: API key is required
|
||||
model:
|
||||
label: Model
|
||||
msg: Model is required
|
||||
add_success: AI settings updated successfully.
|
||||
conversations:
|
||||
topic: Topic
|
||||
helpful: Helpful
|
||||
unhelpful: Unhelpful
|
||||
created: Created
|
||||
action: Action
|
||||
empty: No conversations found.
|
||||
delete_modal:
|
||||
title: Delete conversation
|
||||
content: Are you sure you want to delete this conversation? This is permanent!
|
||||
delete_success: Conversation deleted successfully.
|
||||
mcp:
|
||||
mcp_server:
|
||||
label: MCP server
|
||||
switch: Enabled
|
||||
type:
|
||||
label: Type
|
||||
url:
|
||||
label: URL
|
||||
http_header:
|
||||
label: HTTP header
|
||||
text: Please replace {key} with the API Key.
|
||||
form:
|
||||
optional: (optional)
|
||||
empty: cannot be empty
|
||||
@@ -2347,6 +2437,7 @@ ui:
|
||||
user_normal: This user is already normal.
|
||||
user_suspended: This user has been suspended.
|
||||
user_deleted: This user has been deleted.
|
||||
user_added: User has been added successfully.
|
||||
badge_activated: This badge has been activated.
|
||||
badge_inactivated: This badge has been inactivated.
|
||||
users_deleted: These users have been deleted.
|
||||
|
||||
+95
-4
@@ -234,7 +234,7 @@ backend:
|
||||
other: Brak uprawnień do edycji.
|
||||
content_cannot_empty:
|
||||
other: Content cannot be empty.
|
||||
content_less_than_minumum:
|
||||
content_less_than_minimum:
|
||||
other: Not enough content entered.
|
||||
rank:
|
||||
fail_to_meet_the_condition:
|
||||
@@ -846,6 +846,16 @@ ui:
|
||||
http_403: Błąd HTTP 403
|
||||
logout: Wyloguj się
|
||||
posts: Posts
|
||||
ai_assistant: AI Assistant
|
||||
ai_assistant:
|
||||
description: Got a question? Ask it and get answers, perspectives, and recommendations.
|
||||
recent_conversations: Recent Conversations
|
||||
show_more: Show more
|
||||
new: New chat
|
||||
ai_generate: AI-generated from posts and may not be accurate.
|
||||
copy: Copy
|
||||
ask_a_follow_up: Ask a follow-up
|
||||
ask_placeholder: Ask a question
|
||||
notifications:
|
||||
title: Powiadomienia
|
||||
inbox: Skrzynka odbiorcza
|
||||
@@ -1190,8 +1200,7 @@ ui:
|
||||
search:
|
||||
placeholder: Szukaj
|
||||
footer:
|
||||
build_on: >-
|
||||
Zbudowane na platformie <1> Apache Answer </1> - oprogramowanie open-source, które napędza społeczności pytań i odpowiedzi.<br />Stworzone z miłością © {{cc}}.
|
||||
build_on: Powered by <1> Apache Answer </1>
|
||||
upload_img:
|
||||
name: Zmień
|
||||
loading: Wczytywanie...
|
||||
@@ -1768,6 +1777,7 @@ ui:
|
||||
branding: Marka
|
||||
legal: Prawne
|
||||
write: Pisanie
|
||||
terms: Terms
|
||||
tos: Warunki korzystania z usługi
|
||||
privacy: Prywatność
|
||||
seo: SEO
|
||||
@@ -1778,6 +1788,18 @@ ui:
|
||||
plugins: Wtyczki
|
||||
installed_plugins: Zainstalowane wtyczki
|
||||
apperance: Appearance
|
||||
community: Community
|
||||
advanced: Advanced
|
||||
tags: Tags
|
||||
rules: Rules
|
||||
policies: Policies
|
||||
security: Security
|
||||
files: Files
|
||||
apikeys: API Keys
|
||||
intelligence: Intelligence
|
||||
ai_assistant: AI Assistant
|
||||
ai_settings: AI Settings
|
||||
mcp: MCP
|
||||
website_welcome: Witamy w serwisie {{site_name}}
|
||||
user_center:
|
||||
login: Zaloguj się
|
||||
@@ -2089,7 +2111,7 @@ ui:
|
||||
always_display: Always display external content
|
||||
ask_before_display: Ask before displaying external content
|
||||
write:
|
||||
page_title: Pisanie
|
||||
page_title: Files
|
||||
min_content:
|
||||
label: Minimum question body length
|
||||
text: Minimum allowed question body length in characters.
|
||||
@@ -2147,6 +2169,10 @@ ui:
|
||||
primary_color:
|
||||
label: Kolor podstawowy
|
||||
text: Zmodyfikuj kolory używane przez Twoje motywy.
|
||||
layout:
|
||||
label: Layout
|
||||
full_width: Full-width
|
||||
fixed_width: Fixed-width
|
||||
css_and_html:
|
||||
page_title: CSS i HTML
|
||||
custom_css:
|
||||
@@ -2251,6 +2277,70 @@ ui:
|
||||
show_logs: Wyświetl dzienniki
|
||||
status: Status
|
||||
title: Badges
|
||||
apikeys:
|
||||
title: API Keys
|
||||
add_api_key: Add API Key
|
||||
desc: Description
|
||||
scope: Scope
|
||||
key: Key
|
||||
created: Created
|
||||
last_used: Last used
|
||||
add_or_edit_modal:
|
||||
add_title: Add API Key
|
||||
edit_title: Edit API Key
|
||||
description: Description
|
||||
description_required: Description is required.
|
||||
scope: Scope
|
||||
global: Global
|
||||
read-only: Read-only
|
||||
created_modal:
|
||||
title: API key created
|
||||
api_key: API key
|
||||
description: This key will not be displayed again. Make sure you take a copy before continuing.
|
||||
delete_modal:
|
||||
title: Delete API Key
|
||||
content: Any applications or scripts using this key will no longer be able to access the API. This is permanent!
|
||||
ai_settings:
|
||||
enabled:
|
||||
label: AI enabled
|
||||
check: Enable AI features
|
||||
text: The AI model must be configured correctly before it can be used.
|
||||
provider:
|
||||
label: Provider
|
||||
api_host:
|
||||
label: API host
|
||||
msg: API host is required
|
||||
api_key:
|
||||
label: API key
|
||||
check: Check
|
||||
check_success: "Connection successful."
|
||||
msg: API key is required
|
||||
model:
|
||||
label: Model
|
||||
msg: Model is required
|
||||
add_success: AI settings updated successfully.
|
||||
conversations:
|
||||
topic: Topic
|
||||
helpful: Helpful
|
||||
unhelpful: Unhelpful
|
||||
created: Created
|
||||
action: Action
|
||||
empty: No conversations found.
|
||||
delete_modal:
|
||||
title: Delete conversation
|
||||
content: Are you sure you want to delete this conversation? This is permanent!
|
||||
delete_success: Conversation deleted successfully.
|
||||
mcp:
|
||||
mcp_server:
|
||||
label: MCP server
|
||||
switch: Enabled
|
||||
type:
|
||||
label: Type
|
||||
url:
|
||||
label: URL
|
||||
http_header:
|
||||
label: HTTP header
|
||||
text: Please replace {key} with the API Key.
|
||||
form:
|
||||
optional: (opcjonalne)
|
||||
empty: nie może być puste
|
||||
@@ -2347,6 +2437,7 @@ ui:
|
||||
user_normal: This user is already normal.
|
||||
user_suspended: This user has been suspended.
|
||||
user_deleted: This user has been deleted.
|
||||
user_added: User has been added successfully.
|
||||
badge_activated: This badge has been activated.
|
||||
badge_inactivated: This badge has been inactivated.
|
||||
users_deleted: These users have been deleted.
|
||||
|
||||
+124
-124
@@ -288,7 +288,7 @@ ui:
|
||||
change_email: Modificar e-mail
|
||||
install: Instalação do Resposta
|
||||
upgrade: Atualização do Resposta
|
||||
maintenance: Manutençã do Website
|
||||
maintenance: Manutenção do Website
|
||||
users: Usuários
|
||||
notifications:
|
||||
title: Notificações
|
||||
@@ -327,7 +327,7 @@ ui:
|
||||
empty: Código não pode ser vazio.
|
||||
language:
|
||||
label: Idioma (opcional)
|
||||
placeholder: Tetecção automática
|
||||
placeholder: Detecção automática
|
||||
btn_cancel: Cancelar
|
||||
btn_confirm: Adicionar
|
||||
formula:
|
||||
@@ -351,7 +351,7 @@ ui:
|
||||
image:
|
||||
text: Imagem
|
||||
add_image: Adicionar imagem
|
||||
tab_image: Enviar image,
|
||||
tab_image: Enviar imagem
|
||||
form_image:
|
||||
fields:
|
||||
file:
|
||||
@@ -380,7 +380,7 @@ ui:
|
||||
outdent:
|
||||
text: Não identado
|
||||
italic:
|
||||
text: Emphase
|
||||
text: Ênfase
|
||||
link:
|
||||
text: Superlink (Hyperlink)
|
||||
add_link: Adicionar superlink (hyperlink)
|
||||
@@ -537,7 +537,7 @@ ui:
|
||||
title: Adicionar Pergunta
|
||||
edit_title: Editar Pergunta
|
||||
default_reason: Editar pergunta
|
||||
similar_questions: Similar perguntas
|
||||
similar_questions: Perguntas similares
|
||||
form:
|
||||
fields:
|
||||
revision:
|
||||
@@ -564,10 +564,10 @@ ui:
|
||||
label: Resumo da edição
|
||||
placeholder: >-
|
||||
Explique resumidamente suas alterações (ortografia corrigida, gramática corrigida, formatação aprimorada)
|
||||
btn_post_question: Publicação a sua pergunta
|
||||
btn_post_question: Publicar a sua pergunta
|
||||
btn_save_edits: Salvar edições
|
||||
answer_question: Responda a sua própria pergunta
|
||||
post_question&answer: Publicação a sua pergunta e resposta
|
||||
post_question&answer: Publicar a sua pergunta e resposta
|
||||
tag_selector:
|
||||
add_btn: Adicionar marcador
|
||||
create_btn: Criar novo marcador
|
||||
@@ -589,7 +589,7 @@ ui:
|
||||
placeholder: Procurar
|
||||
footer:
|
||||
build_on: >-
|
||||
Built on <1> Answer </1>- the open-source software that powers Q&A communities.<br />Made with love © {{cc}}.
|
||||
Desenvolvido com base no <1> Answer </1> — o software de código aberto que alimenta comunidades de perguntas e respostas.<br />Feito com amor © {{cc}}.
|
||||
upload_img:
|
||||
name: Mudar
|
||||
loading: carregando...
|
||||
@@ -604,13 +604,13 @@ ui:
|
||||
info: "Se não chegar, verifique sua pasta de spam."
|
||||
another: >-
|
||||
Enviamos outro e-mail de ativação para você em <bold>{{mail}}</bold>. Pode levar alguns minutos para chegar; certifique-se de verificar sua pasta de spam.
|
||||
btn_name: Resend activation email
|
||||
btn_name: Reenviar e-mail de ativação
|
||||
change_btn_name: Mudar email
|
||||
msg:
|
||||
empty: Não pode ser vazio.
|
||||
login:
|
||||
page_title: Bem vindo ao {{site_name}}
|
||||
login_to_continue: Entre para continue
|
||||
login_to_continue: Entre para continuar
|
||||
info_sign: Não possui uma conta? <1>Cadastrar-se</1>
|
||||
info_login: Já possui uma conta? <1>Entre</1>
|
||||
agreements: Ao se registrar, você concorda com as <1>políticas de privacidades</1> e os <3>termos de serviços</3>.
|
||||
@@ -776,9 +776,9 @@ ui:
|
||||
delete:
|
||||
title: Excluir esta postagem
|
||||
question: >-
|
||||
Nós não recomendamos <strong>excluindo perguntas com respostas</strong> porque isso priva os futuros leitores desse conhecimento.</p><p>Repeated deletion of answered questions can result in a sua account being blocked from asking. Você tem certeza que deseja deletar?
|
||||
Nós não recomendamos <strong> excluir perguntas com respostas</strong> porque isso priva os futuros leitores desse conhecimento.</p><p>A exclusão repetida de perguntas respondidas pode resultar no bloqueio de perguntas de sua conta. Você tem certeza que deseja excluir?
|
||||
answer_accepted: >-
|
||||
<p>Nós não recomendamos <strong>deleting accepted answer</strong> porque isso priva os futuros leitores desse conhecimento. </p> Repeated deletion of accepted answers can result in a sua account being blocked from answering. Você tem certeza que deseja deletar?
|
||||
<p>Não recomendamos <strong>excluir resposta aceita</strong> porque isso priva os futuros leitores desse conhecimento. </p> A exclusão repetida de respostas aceitas pode resultar no bloqueio de respostas de uma conta sua. Você tem certeza que deseja excluir?
|
||||
other: Você tem certeza que deseja deletar?
|
||||
tip_question_deleted: Esta postagem foi deletada
|
||||
tip_answer_deleted: Esta resposta foi deletada
|
||||
@@ -834,7 +834,7 @@ ui:
|
||||
link: Continuar para a página inicial.
|
||||
invalid: >-
|
||||
Desculpe, este link de confirmação não é mais válido. Talvez a sua já está ativa.
|
||||
confirm_new_email: Your email has been updated.
|
||||
confirm_new_email: Seu e-mail foi atualizado.
|
||||
confirm_new_email_invalid: >-
|
||||
Desculpe, este link de confirmação não é mais válido. Talvez o seu e-mail já tenha sido alterado.
|
||||
unsubscribe:
|
||||
@@ -846,7 +846,7 @@ ui:
|
||||
following_tags: Seguindo Marcadores
|
||||
edit: Editar
|
||||
save: Salvar
|
||||
follow_tag_tip: Seguir tags to curate a sua lista de perguntas.
|
||||
follow_tag_tip: Siga as tags para selecionar sua lista de perguntas.
|
||||
hot_questions: Perguntas quentes
|
||||
all_questions: Todas Perguntas
|
||||
x_questions: "{{ count }} perguntas"
|
||||
@@ -878,7 +878,7 @@ ui:
|
||||
score: Pontuação
|
||||
edit_profile: Editar Perfil
|
||||
visited_x_days: "Visitado {{ count }} dias"
|
||||
viewed: Viewed
|
||||
viewed: Visualizado
|
||||
joined: Ingressou
|
||||
last_login: Visto
|
||||
about_me: Sobre mim
|
||||
@@ -900,13 +900,13 @@ ui:
|
||||
x_questions: perguntas
|
||||
install:
|
||||
title: Instalação
|
||||
next: Proximo
|
||||
next: Próximo
|
||||
done: Completo
|
||||
config_yaml_error: Não é possível criar o arquivo config.yaml.
|
||||
lang:
|
||||
label: Por favor Escolha um Idioma
|
||||
db_type:
|
||||
label: Database Engine
|
||||
label: Mecanismo de banco de dados
|
||||
db_username:
|
||||
label: Nome de usuário
|
||||
placeholder: root
|
||||
@@ -916,68 +916,68 @@ ui:
|
||||
placeholder: root
|
||||
msg: Senha não pode ser vazio.
|
||||
db_host:
|
||||
label: Database Host
|
||||
label: Host do banco de dados
|
||||
placeholder: "db:3306"
|
||||
msg: Database Host não pode ser vazio.
|
||||
msg: Host de banco de dados não pode ficar vazio.
|
||||
db_name:
|
||||
label: Database Nome
|
||||
label: Nome do banco de dados
|
||||
placeholder: answer
|
||||
msg: Database Nome não pode ser vazio.
|
||||
msg: O nome do banco de dados não pode ficar vazio.
|
||||
db_file:
|
||||
label: Database File
|
||||
label: Arquivo de banco de dados
|
||||
placeholder: /data/answer.db
|
||||
msg: Database File não pode ser vazio.
|
||||
msg: O arquivo de banco de dados não pode ficar vazio.
|
||||
config_yaml:
|
||||
title: Create config.yaml
|
||||
label: The config.yaml file created.
|
||||
title: Criar config.yaml
|
||||
label: O arquivo config.yaml foi criado.
|
||||
desc: >-
|
||||
You can create the <1>config.yaml</1> file manually in the <1>/var/wwww/xxx/</1> directory and paste the following text into it.
|
||||
info: After you've done that, click "Next" button.
|
||||
site_information: Site Information
|
||||
Você pode criar o arquivo <1>config.yaml</1> manualmente no diretório <1>/var/www/xxx/</1> e colar o seguinte texto nele.
|
||||
info: Depois de fazer isso, clique no botão "Avançar".
|
||||
site_information: Informações do site
|
||||
admin_account: Administrador Conta
|
||||
site_name:
|
||||
label: Site Nome
|
||||
msg: Site Nome não pode ser vazio.
|
||||
site_url:
|
||||
label: Site URL
|
||||
text: The address of a sua site.
|
||||
text: O endereço do seu site.
|
||||
msg:
|
||||
empty: Site URL não pode ser vazio.
|
||||
incorrect: Site URL incorrect format.
|
||||
incorrect: Formato incorreto da URL do site.
|
||||
contact_email:
|
||||
label: E-mail par contato
|
||||
text: Email address of key contact responsible for this site.
|
||||
label: E-mail para contato
|
||||
text: Endereço de e-mail do contato principal responsável por este site.
|
||||
msg:
|
||||
empty: E-mail par contato não pode ser vazio.
|
||||
incorrect: E-mail par contato incorrect format.
|
||||
empty: E-mail para contato não pode ser vazio.
|
||||
incorrect: E-mail para contato em formato incorreto.
|
||||
admin_name:
|
||||
label: Nome
|
||||
msg: Nome não pode ser vazio.
|
||||
admin_password:
|
||||
label: Senha
|
||||
text: >-
|
||||
You will need this password to log in. Por favor store it in a secure location.
|
||||
msg: Senha não pode ser vazio.
|
||||
Você precisará dessa senha para efetuar login. Por favor, guarde-a em um local seguro.
|
||||
msg: Senha não pode ser vazia.
|
||||
admin_email:
|
||||
label: Email
|
||||
text: You will need this email to log in.
|
||||
text: Você precisará deste e-mail para fazer login.
|
||||
msg:
|
||||
empty: Email não pode ser vazio.
|
||||
incorrect: Email incorrect format.
|
||||
ready_title: Your Resposta is Ready!
|
||||
incorrect: Formato de e-mail incorreto.
|
||||
ready_title: Sua resposta está pronta!
|
||||
ready_desc: >-
|
||||
If you ever feel like changing more settings, visit <1>admin section</1>; find it in the site menu.
|
||||
good_luck: "Have fun, and good luck!"
|
||||
warn_title: Warning
|
||||
Se você quiser alterar mais configurações, visite a <1>seção de administração</1>; encontre-a no menu do site.
|
||||
good_luck: "Divirta-se e boa sorte!"
|
||||
warn_title: Aviso
|
||||
warn_desc: >-
|
||||
The file <1>config.yaml</1> already exists. If you need to reset any of the configuration items in this file, please delete it first.
|
||||
install_now: You may try <1>installing now</1>.
|
||||
installed: Already installed
|
||||
O arquivo <1>config.yaml</1> já existe. Se precisar redefinir algum item de configuração neste arquivo, exclua-o primeiro.
|
||||
install_now: Você pode tentar <1>instalar agora</1>.
|
||||
installed: Já instalado
|
||||
installed_desc: >-
|
||||
You appear to have already installed. To reinstall please clear a sua old database tables first.
|
||||
db_failed: Database connection failed
|
||||
Parece que você já instalou. Para reinstalar, limpe primeiro as tabelas antigas do seu banco de dados.
|
||||
db_failed: Falha na conexão do banco de dados
|
||||
db_failed_desc: >-
|
||||
This either means that the database information in a sua <1>config.yaml</1> file is incorrect or that contact with the database server could not be established. This could mean a sua host's database server is down.
|
||||
Isso significa que as informações do banco de dados em um arquivo <1>config.yaml</1> do SUA estão incorretas ou que o contato com o servidor do banco de dados não pôde ser estabelecido. Isso pode significar que o servidor de banco de dados de um host SUA está inativo.
|
||||
counts:
|
||||
views: visualizações
|
||||
Votos: votos
|
||||
@@ -987,7 +987,7 @@ ui:
|
||||
desc: "Infelizmente, esta postagem não existe mais."
|
||||
back_home: Voltar para a página inicial
|
||||
page_50X:
|
||||
desc: O servidor encontrou um erro e não pôde concluir uma solicitação sua.
|
||||
desc: O servidor encontrou um erro e não pôde concluir sua solicitação.
|
||||
back_home: Voltar para a página inicial
|
||||
page_maintenance:
|
||||
desc: "Estamos em manutenção, voltaremos em breve."
|
||||
@@ -1037,7 +1037,7 @@ ui:
|
||||
answer_links: Links das Respostas
|
||||
documents: Documentos
|
||||
feedback: Opinião
|
||||
support: Supporte
|
||||
support: Suporte
|
||||
review: Revisar
|
||||
config: Configurações
|
||||
update_to: Atualizar ao
|
||||
@@ -1233,7 +1233,7 @@ ui:
|
||||
smtp_port:
|
||||
label: SMTP Port
|
||||
msg: SMTP port must be number 1 ~ 65535.
|
||||
text: The port to a sua mail server.
|
||||
text: A porta para seu servidor de e-mail.
|
||||
smtp_username:
|
||||
label: SMTP Nome de usuário
|
||||
msg: SMTP username não pode ser vazio.
|
||||
@@ -1241,9 +1241,9 @@ ui:
|
||||
label: SMTP Senha
|
||||
msg: SMTP password não pode ser vazio.
|
||||
test_email_recipient:
|
||||
label: Test Email Recipients
|
||||
text: Provide email address that will receive test sends.
|
||||
msg: Test email recipients is invalid
|
||||
label: Destinatários de e-mail de teste
|
||||
text: Forneça o endereço de e-mail que receberá os envios de testes.
|
||||
msg: Os destinatários do e-mail de teste são inválidos
|
||||
smtp_authentication:
|
||||
label: Enable authentication
|
||||
title: SMTP Authentication
|
||||
@@ -1255,127 +1255,127 @@ ui:
|
||||
logo:
|
||||
label: Logo (opcional)
|
||||
msg: Logo não pode ser vazio.
|
||||
text: The logo image at the top left of a sua site. Use a wide rectangular image with a height of 56 and an aspect ratio greater than 3:1. If left blank, the site title text will be shown.
|
||||
text: A imagem do logotipo no canto superior esquerdo do seu site. Use uma imagem retangular larga com altura de 56 e proporção maior que 3:1. Se deixada em branco, o texto do título do site será exibido.
|
||||
mobile_logo:
|
||||
label: Mobile Logo (opcional)
|
||||
text: The logo used on mobile version of a sua site. Use a wide rectangular image with a height of 56. If left blank, the image from the "logo" setting will be used.
|
||||
text: O logotipo usado na versão mobile do seu site. Use uma imagem retangular larga com altura de 56. Se deixado em branco, a imagem da configuração "logotipo" será usada.
|
||||
square_icon:
|
||||
label: Square Icon (opcional)
|
||||
msg: Square icon não pode ser vazio.
|
||||
text: Imagem used as the base for metadata icons. Should ideally be larger than 512x512.
|
||||
text: Imagem usada como base para ícones de metadados. Idealmente, deve ser maior que 512x512.
|
||||
favicon:
|
||||
label: Favicon (opcional)
|
||||
text: A favicon for a sua site. To work correctly over a CDN it must be a png. Will be resized to 32x32. If left blank, "square icon" will be used.
|
||||
text: Um favicon para o seu site. Para funcionar corretamente em uma CDN, ele deve ser um png. Será redimensionado para 32x32. Se deixado em branco, o "ícone quadrado" será usado.
|
||||
legal:
|
||||
page_title: Legal
|
||||
terms_of_service:
|
||||
label: Terms of Service
|
||||
text: "You can add terms of service content here. If you already have a document hosted elsewhere, provide the full URL here."
|
||||
label: Termos de Serviço
|
||||
text: "Você pode adicionar conteúdo dos termos de serviço aqui. Se você já possui um documento hospedado em outro lugar, informe o URL completo aqui."
|
||||
privacy_policy:
|
||||
label: Privacy Policy
|
||||
text: "You can add privacy policy content here. If you already have a document hosted elsewhere, provide the full URL here."
|
||||
label: Política de Privacidade
|
||||
text: "Você pode adicionar o conteúdo da política de privacidade aqui. Se você já possui um documento hospedado em outro lugar, informe o URL completo aqui."
|
||||
write:
|
||||
page_title: Write
|
||||
page_title: Escrever
|
||||
recommend_tags:
|
||||
label: Recommend Marcadores
|
||||
text: "Por favor input tag slug above, one tag per line."
|
||||
label: Recomendar Marcadores
|
||||
text: "Por favor, insira o slug da tag acima, uma tag por linha."
|
||||
required_tag:
|
||||
title: Required Tag
|
||||
label: Set recommend tag as requirido
|
||||
text: "Every new question must have ao menos one recommend tag."
|
||||
title: Tag necessária
|
||||
label: Definir tag recomendada como necessária
|
||||
text: "Cada nova pergunta deve ter pelo menos uma tag de recomendação."
|
||||
reserved_tags:
|
||||
label: Reserved Marcadores
|
||||
text: "Reserved tags can only be added to a post by moderator."
|
||||
label: Marcadores Reservados
|
||||
text: "Tags reservadas só podem ser adicionadas a uma postagem pelo moderador."
|
||||
seo:
|
||||
page_title: SEO
|
||||
permalink:
|
||||
label: Permalink
|
||||
text: Custom URL structures can improve the usability, and forward-compatibility of a sua links.
|
||||
text: Estruturas de URL personalizadas podem melhorar a usabilidade e a compatibilidade futura de seus links.
|
||||
robots:
|
||||
label: robots.txt
|
||||
text: This will permanently override any related site settings.
|
||||
text: Isso substituirá permanentemente todas as configurações relacionadas do site.
|
||||
Temas:
|
||||
page_title: Temas
|
||||
Temas:
|
||||
label: Temas
|
||||
text: Select an existing Tema.
|
||||
text: Selecione um tema existente.
|
||||
navbar_style:
|
||||
label: Navbar Style
|
||||
text: Select an existing Tema.
|
||||
label: Estilo da barra de navegação
|
||||
text: Selecione um tema existente.
|
||||
primary_color:
|
||||
label: Primary Color
|
||||
text: Modify the colors used by a sua Temas
|
||||
label: Cor primária
|
||||
text: Modifique as cores usadas por seus Temas
|
||||
css_and_html:
|
||||
page_title: CSS and HTML
|
||||
page_title: CSS e HTML
|
||||
custom_css:
|
||||
label: Custom CSS
|
||||
text: This will insert as <link>
|
||||
text: Isto será inserido como <link>
|
||||
head:
|
||||
label: Head
|
||||
text: This will insert before </head>
|
||||
text: Isto será inserido antes de </head>
|
||||
header:
|
||||
label: Header
|
||||
text: This will insert after <body>
|
||||
text: Isto será inserido após <body>
|
||||
footer:
|
||||
label: Footer
|
||||
text: This will insert before </html>.
|
||||
text: Isso será inserido antes de </html>.
|
||||
login:
|
||||
page_title: Login
|
||||
membership:
|
||||
title: Membership
|
||||
label: Allow new registrations
|
||||
text: Turn off to prevent anyone from creating a new account.
|
||||
title: Associação
|
||||
label: Permitir novos registros
|
||||
text: Desative para impedir que alguém crie uma nova conta.
|
||||
private:
|
||||
title: Private
|
||||
title: Privado
|
||||
label: Login requirido
|
||||
text: Only logged in users can access this community.
|
||||
text: Somente usuários logados podem acessar esta comunidade.
|
||||
form:
|
||||
empty: não pode ser vazio
|
||||
invalid: is invalid
|
||||
invalid: é inválido
|
||||
btn_submit: Salvar
|
||||
not_found_props: "Required property {{ key }} not found."
|
||||
not_found_props: "Propriedade necessária {{ key }} não encontrada."
|
||||
page_review:
|
||||
review: Revisar
|
||||
proposed: proposed
|
||||
question_edit: Pergunta edit
|
||||
answer_edit: Resposta edit
|
||||
tag_edit: Tag edit
|
||||
edit_summary: Editar summary
|
||||
edit_question: Editar question
|
||||
edit_answer: Editar answer
|
||||
proposed: proposta
|
||||
question_edit: Editar Pergunta
|
||||
answer_edit: Editar Resposta
|
||||
tag_edit: Editar Tag
|
||||
edit_summary: Editar resumo
|
||||
edit_question: Editar pergunta
|
||||
edit_answer: Editar resposta
|
||||
edit_tag: Editar tag
|
||||
empty: No review tasks left.
|
||||
empty: Não há mais tarefas de revisão.
|
||||
timeline:
|
||||
undeleted: undeleted
|
||||
deleted: deleted
|
||||
downvote: downvote
|
||||
upvote: upvote
|
||||
accept: accept
|
||||
cancelled: cancelled
|
||||
commented: commented
|
||||
undeleted: não excluído
|
||||
deleted: apagado
|
||||
downvote: voto negativo
|
||||
upvote: voto positivo
|
||||
accept: aceitar
|
||||
cancelled: cancelado
|
||||
commented: comentado
|
||||
rollback: rollback
|
||||
edited: edited
|
||||
answered: answered
|
||||
asked: asked
|
||||
closed: closed
|
||||
reopened: reopened
|
||||
created: created
|
||||
title: "Histórico for"
|
||||
tag_title: "Timeline for"
|
||||
show_Votos: "Show Votos"
|
||||
edited: editado
|
||||
answered: respondido
|
||||
asked: perguntado
|
||||
closed: fechado
|
||||
reopened: reaberto
|
||||
created: criado
|
||||
title: "Histórico para"
|
||||
tag_title: "Linha do tempo para"
|
||||
show_Votos: "Mostrar votos"
|
||||
n_or_a: N/A
|
||||
title_for_question: "Timeline for"
|
||||
title_for_answer: "Timeline for answer to {{ title }} by {{ author }}"
|
||||
title_for_tag: "Timeline for tag"
|
||||
title_for_question: "Linha do tempo para"
|
||||
title_for_answer: "Linha do tempo para resposta a {{ title }} por {{ author }}"
|
||||
title_for_tag: "Linha do tempo para tag"
|
||||
datetime: Datetime
|
||||
type: Type
|
||||
by: By
|
||||
comment: Comment
|
||||
no_data: "We couldn't find anything."
|
||||
type: Tipo
|
||||
by: Por
|
||||
comment: Comentário
|
||||
no_data: "Não conseguimos encontrar nada."
|
||||
users:
|
||||
title: Usuários
|
||||
users_with_the_most_reputation: Usuários with the highest reputation scores
|
||||
users_with_the_most_vote: Usuários who voted the most
|
||||
staffs: Our community staff
|
||||
reputation: reputation
|
||||
users_with_the_most_reputation: Usuários com as maiores pontuações de reputação
|
||||
users_with_the_most_vote: Usuários que mais votaram
|
||||
staffs: Nossa equipe comunitária
|
||||
reputation: reputação
|
||||
Votos: Votos
|
||||
|
||||
+95
-4
@@ -234,7 +234,7 @@ backend:
|
||||
other: Sem permissão para atualizar.
|
||||
content_cannot_empty:
|
||||
other: Content cannot be empty.
|
||||
content_less_than_minumum:
|
||||
content_less_than_minimum:
|
||||
other: Not enough content entered.
|
||||
rank:
|
||||
fail_to_meet_the_condition:
|
||||
@@ -846,6 +846,16 @@ ui:
|
||||
http_403: HTTP Erro 403
|
||||
logout: Encerrar Sessão
|
||||
posts: Posts
|
||||
ai_assistant: AI Assistant
|
||||
ai_assistant:
|
||||
description: Got a question? Ask it and get answers, perspectives, and recommendations.
|
||||
recent_conversations: Recent Conversations
|
||||
show_more: Show more
|
||||
new: New chat
|
||||
ai_generate: AI-generated from posts and may not be accurate.
|
||||
copy: Copy
|
||||
ask_a_follow_up: Ask a follow-up
|
||||
ask_placeholder: Ask a question
|
||||
notifications:
|
||||
title: Notificações
|
||||
inbox: Caixa de entrada
|
||||
@@ -1190,8 +1200,7 @@ ui:
|
||||
search:
|
||||
placeholder: Procurar
|
||||
footer:
|
||||
build_on: >-
|
||||
Construído com <1> Apache answer </1> o software de código aberto que ajuda comunidades de Q&A.<br />Feito com amor © {{cc}}.
|
||||
build_on: Powered by <1> Apache Answer </1>
|
||||
upload_img:
|
||||
name: Mudar
|
||||
loading: carregando...
|
||||
@@ -1768,6 +1777,7 @@ ui:
|
||||
branding: Marca
|
||||
legal: Informação legal
|
||||
write: Escrever
|
||||
terms: Terms
|
||||
tos: Termos de Serviços
|
||||
privacy: Privacidade
|
||||
seo: SEO
|
||||
@@ -1778,6 +1788,18 @@ ui:
|
||||
plugins: Extensões
|
||||
installed_plugins: Plugins instalados
|
||||
apperance: Appearance
|
||||
community: Community
|
||||
advanced: Advanced
|
||||
tags: Tags
|
||||
rules: Rules
|
||||
policies: Policies
|
||||
security: Security
|
||||
files: Files
|
||||
apikeys: API Keys
|
||||
intelligence: Intelligence
|
||||
ai_assistant: AI Assistant
|
||||
ai_settings: AI Settings
|
||||
mcp: MCP
|
||||
website_welcome: Bem vindo(a) ao {{site_name}}
|
||||
user_center:
|
||||
login: Entrar
|
||||
@@ -2089,7 +2111,7 @@ ui:
|
||||
always_display: Always display external content
|
||||
ask_before_display: Ask before displaying external content
|
||||
write:
|
||||
page_title: Escrever
|
||||
page_title: Files
|
||||
min_content:
|
||||
label: Minimum question body length
|
||||
text: Minimum allowed question body length in characters.
|
||||
@@ -2147,6 +2169,10 @@ ui:
|
||||
primary_color:
|
||||
label: Cor primária
|
||||
text: Modifica as cores usadas por seus temas
|
||||
layout:
|
||||
label: Layout
|
||||
full_width: Full-width
|
||||
fixed_width: Fixed-width
|
||||
css_and_html:
|
||||
page_title: CSS e HTML
|
||||
custom_css:
|
||||
@@ -2251,6 +2277,70 @@ ui:
|
||||
show_logs: Mostrar registros
|
||||
status: Status
|
||||
title: Emblemas
|
||||
apikeys:
|
||||
title: API Keys
|
||||
add_api_key: Add API Key
|
||||
desc: Description
|
||||
scope: Scope
|
||||
key: Key
|
||||
created: Created
|
||||
last_used: Last used
|
||||
add_or_edit_modal:
|
||||
add_title: Add API Key
|
||||
edit_title: Edit API Key
|
||||
description: Description
|
||||
description_required: Description is required.
|
||||
scope: Scope
|
||||
global: Global
|
||||
read-only: Read-only
|
||||
created_modal:
|
||||
title: API key created
|
||||
api_key: API key
|
||||
description: This key will not be displayed again. Make sure you take a copy before continuing.
|
||||
delete_modal:
|
||||
title: Delete API Key
|
||||
content: Any applications or scripts using this key will no longer be able to access the API. This is permanent!
|
||||
ai_settings:
|
||||
enabled:
|
||||
label: AI enabled
|
||||
check: Enable AI features
|
||||
text: The AI model must be configured correctly before it can be used.
|
||||
provider:
|
||||
label: Provider
|
||||
api_host:
|
||||
label: API host
|
||||
msg: API host is required
|
||||
api_key:
|
||||
label: API key
|
||||
check: Check
|
||||
check_success: "Connection successful."
|
||||
msg: API key is required
|
||||
model:
|
||||
label: Model
|
||||
msg: Model is required
|
||||
add_success: AI settings updated successfully.
|
||||
conversations:
|
||||
topic: Topic
|
||||
helpful: Helpful
|
||||
unhelpful: Unhelpful
|
||||
created: Created
|
||||
action: Action
|
||||
empty: No conversations found.
|
||||
delete_modal:
|
||||
title: Delete conversation
|
||||
content: Are you sure you want to delete this conversation? This is permanent!
|
||||
delete_success: Conversation deleted successfully.
|
||||
mcp:
|
||||
mcp_server:
|
||||
label: MCP server
|
||||
switch: Enabled
|
||||
type:
|
||||
label: Type
|
||||
url:
|
||||
label: URL
|
||||
http_header:
|
||||
label: HTTP header
|
||||
text: Please replace {key} with the API Key.
|
||||
form:
|
||||
optional: (opcional)
|
||||
empty: não pode ser vazio
|
||||
@@ -2347,6 +2437,7 @@ ui:
|
||||
user_normal: Este usuário já está normal.
|
||||
user_suspended: Este usuário foi suspenso.
|
||||
user_deleted: Este usuário foi removido.
|
||||
user_added: User has been added successfully.
|
||||
badge_activated: Este emblema foi ativado.
|
||||
badge_inactivated: Este emblema foi desativado.
|
||||
users_deleted: These users have been deleted.
|
||||
|
||||
+95
-4
@@ -234,7 +234,7 @@ backend:
|
||||
other: Nu aveți permisiunea de a actualiza.
|
||||
content_cannot_empty:
|
||||
other: Conținutul nu poate fi gol.
|
||||
content_less_than_minumum:
|
||||
content_less_than_minimum:
|
||||
other: Not enough content entered.
|
||||
rank:
|
||||
fail_to_meet_the_condition:
|
||||
@@ -846,6 +846,16 @@ ui:
|
||||
http_403: Eroare HTTP 403
|
||||
logout: Deconectare
|
||||
posts: Posts
|
||||
ai_assistant: AI Assistant
|
||||
ai_assistant:
|
||||
description: Got a question? Ask it and get answers, perspectives, and recommendations.
|
||||
recent_conversations: Recent Conversations
|
||||
show_more: Show more
|
||||
new: New chat
|
||||
ai_generate: AI-generated from posts and may not be accurate.
|
||||
copy: Copy
|
||||
ask_a_follow_up: Ask a follow-up
|
||||
ask_placeholder: Ask a question
|
||||
notifications:
|
||||
title: Notificări
|
||||
inbox: Mesaje primite
|
||||
@@ -1190,8 +1200,7 @@ ui:
|
||||
search:
|
||||
placeholder: Caută
|
||||
footer:
|
||||
build_on: >-
|
||||
Susținut de <1> Apache Răspuns </1>- software-ul open-source care asigură Q&A comunități.<br />Făcut cu dragoste ©️ {{cc}}.
|
||||
build_on: Powered by <1> Apache Answer </1>
|
||||
upload_img:
|
||||
name: Schimbare
|
||||
loading: încarcare...
|
||||
@@ -1768,6 +1777,7 @@ ui:
|
||||
branding: Marcă
|
||||
legal: Juridic
|
||||
write: Scrie
|
||||
terms: Terms
|
||||
tos: Condiții de utilizare
|
||||
privacy: Confidențialitate
|
||||
seo: SEO
|
||||
@@ -1778,6 +1788,18 @@ ui:
|
||||
plugins: Extensii
|
||||
installed_plugins: Extensii instalate
|
||||
apperance: Appearance
|
||||
community: Community
|
||||
advanced: Advanced
|
||||
tags: Tags
|
||||
rules: Rules
|
||||
policies: Policies
|
||||
security: Security
|
||||
files: Files
|
||||
apikeys: API Keys
|
||||
intelligence: Intelligence
|
||||
ai_assistant: AI Assistant
|
||||
ai_settings: AI Settings
|
||||
mcp: MCP
|
||||
website_welcome: Bun venit la {{site_name}}
|
||||
user_center:
|
||||
login: Autentifică-te
|
||||
@@ -2089,7 +2111,7 @@ ui:
|
||||
always_display: Always display external content
|
||||
ask_before_display: Ask before displaying external content
|
||||
write:
|
||||
page_title: Scrie
|
||||
page_title: Files
|
||||
min_content:
|
||||
label: Minimum question body length
|
||||
text: Minimum allowed question body length in characters.
|
||||
@@ -2147,6 +2169,10 @@ ui:
|
||||
primary_color:
|
||||
label: Culoare primară
|
||||
text: Modifică culorile folosite de temele tale
|
||||
layout:
|
||||
label: Layout
|
||||
full_width: Full-width
|
||||
fixed_width: Fixed-width
|
||||
css_and_html:
|
||||
page_title: CSS și HTML
|
||||
custom_css:
|
||||
@@ -2251,6 +2277,70 @@ ui:
|
||||
show_logs: Show logs
|
||||
status: Status
|
||||
title: Badges
|
||||
apikeys:
|
||||
title: API Keys
|
||||
add_api_key: Add API Key
|
||||
desc: Description
|
||||
scope: Scope
|
||||
key: Key
|
||||
created: Created
|
||||
last_used: Last used
|
||||
add_or_edit_modal:
|
||||
add_title: Add API Key
|
||||
edit_title: Edit API Key
|
||||
description: Description
|
||||
description_required: Description is required.
|
||||
scope: Scope
|
||||
global: Global
|
||||
read-only: Read-only
|
||||
created_modal:
|
||||
title: API key created
|
||||
api_key: API key
|
||||
description: This key will not be displayed again. Make sure you take a copy before continuing.
|
||||
delete_modal:
|
||||
title: Delete API Key
|
||||
content: Any applications or scripts using this key will no longer be able to access the API. This is permanent!
|
||||
ai_settings:
|
||||
enabled:
|
||||
label: AI enabled
|
||||
check: Enable AI features
|
||||
text: The AI model must be configured correctly before it can be used.
|
||||
provider:
|
||||
label: Provider
|
||||
api_host:
|
||||
label: API host
|
||||
msg: API host is required
|
||||
api_key:
|
||||
label: API key
|
||||
check: Check
|
||||
check_success: "Connection successful."
|
||||
msg: API key is required
|
||||
model:
|
||||
label: Model
|
||||
msg: Model is required
|
||||
add_success: AI settings updated successfully.
|
||||
conversations:
|
||||
topic: Topic
|
||||
helpful: Helpful
|
||||
unhelpful: Unhelpful
|
||||
created: Created
|
||||
action: Action
|
||||
empty: No conversations found.
|
||||
delete_modal:
|
||||
title: Delete conversation
|
||||
content: Are you sure you want to delete this conversation? This is permanent!
|
||||
delete_success: Conversation deleted successfully.
|
||||
mcp:
|
||||
mcp_server:
|
||||
label: MCP server
|
||||
switch: Enabled
|
||||
type:
|
||||
label: Type
|
||||
url:
|
||||
label: URL
|
||||
http_header:
|
||||
label: HTTP header
|
||||
text: Please replace {key} with the API Key.
|
||||
form:
|
||||
optional: (opțional)
|
||||
empty: nu poate fi lăsat necompletat
|
||||
@@ -2347,6 +2437,7 @@ ui:
|
||||
user_normal: This user is already normal.
|
||||
user_suspended: This user has been suspended.
|
||||
user_deleted: This user has been deleted.
|
||||
user_added: User has been added successfully.
|
||||
badge_activated: This badge has been activated.
|
||||
badge_inactivated: This badge has been inactivated.
|
||||
users_deleted: These users have been deleted.
|
||||
|
||||
+100
-9
@@ -58,7 +58,7 @@ backend:
|
||||
undelete:
|
||||
other: Отменить удаление
|
||||
merge:
|
||||
other: Merge
|
||||
other: Объединить
|
||||
role:
|
||||
name:
|
||||
user:
|
||||
@@ -173,7 +173,7 @@ backend:
|
||||
question_closed_cannot_add:
|
||||
other: Вопросы закрыты и не могут быть добавлены.
|
||||
content_cannot_empty:
|
||||
other: Answer content cannot be empty.
|
||||
other: Содержимое ответа не может быть пустым.
|
||||
comment:
|
||||
edit_without_permission:
|
||||
other: Комментарий не может редактироваться.
|
||||
@@ -233,8 +233,8 @@ backend:
|
||||
cannot_update:
|
||||
other: Нет разрешения на обновление.
|
||||
content_cannot_empty:
|
||||
other: Content cannot be empty.
|
||||
content_less_than_minumum:
|
||||
other: .
|
||||
content_less_than_minimum:
|
||||
other: Not enough content entered.
|
||||
rank:
|
||||
fail_to_meet_the_condition:
|
||||
@@ -698,7 +698,7 @@ backend:
|
||||
name:
|
||||
other: Anniversary
|
||||
desc:
|
||||
other: Active member for a year, posted at least once.
|
||||
other: Активный участник на год, опубликовал по крайней мере один раз.
|
||||
appreciated:
|
||||
name:
|
||||
other: Appreciated
|
||||
@@ -846,6 +846,16 @@ ui:
|
||||
http_403: Ошибка HTTP 403
|
||||
logout: Выйти
|
||||
posts: Posts
|
||||
ai_assistant: AI Assistant
|
||||
ai_assistant:
|
||||
description: Got a question? Ask it and get answers, perspectives, and recommendations.
|
||||
recent_conversations: Recent Conversations
|
||||
show_more: Show more
|
||||
new: New chat
|
||||
ai_generate: AI-generated from posts and may not be accurate.
|
||||
copy: Copy
|
||||
ask_a_follow_up: Ask a follow-up
|
||||
ask_placeholder: Ask a question
|
||||
notifications:
|
||||
title: Уведомления
|
||||
inbox: Входящие
|
||||
@@ -1190,8 +1200,7 @@ ui:
|
||||
search:
|
||||
placeholder: Поиск
|
||||
footer:
|
||||
build_on: >-
|
||||
Работает на <1> Apache Answer </1> - программном обеспечении с открытым исходным кодом, которое поддерживает сообщества вопросов и ответов.<br />Сделано с любовью © {{cc}}.
|
||||
build_on: Powered by <1> Apache Answer </1>
|
||||
upload_img:
|
||||
name: Изменить
|
||||
loading: загрузка...
|
||||
@@ -1445,7 +1454,7 @@ ui:
|
||||
content: Are you sure you want to list?
|
||||
unlist:
|
||||
confirm_btn: Убрать из списка
|
||||
title:
|
||||
title: Unlist this post
|
||||
content: Are you sure you want to unlist?
|
||||
pin:
|
||||
title: Закрепить сообщение
|
||||
@@ -1768,6 +1777,7 @@ ui:
|
||||
branding: Фирменное оформление
|
||||
legal: Правовая информация
|
||||
write: Написать
|
||||
terms: Terms
|
||||
tos: Пользовательское Соглашение
|
||||
privacy: Конфиденциальность
|
||||
seo: SEO
|
||||
@@ -1778,6 +1788,18 @@ ui:
|
||||
plugins: Плагины
|
||||
installed_plugins: Установленные плагины
|
||||
apperance: Appearance
|
||||
community: Community
|
||||
advanced: Advanced
|
||||
tags: Tags
|
||||
rules: Rules
|
||||
policies: Policies
|
||||
security: Security
|
||||
files: Files
|
||||
apikeys: API Keys
|
||||
intelligence: Intelligence
|
||||
ai_assistant: AI Assistant
|
||||
ai_settings: AI Settings
|
||||
mcp: MCP
|
||||
website_welcome: Добро пожаловать на {{site_name}}
|
||||
user_center:
|
||||
login: Вход
|
||||
@@ -2089,7 +2111,7 @@ ui:
|
||||
always_display: Always display external content
|
||||
ask_before_display: Ask before displaying external content
|
||||
write:
|
||||
page_title: Написать
|
||||
page_title: Files
|
||||
min_content:
|
||||
label: Minimum question body length
|
||||
text: Minimum allowed question body length in characters.
|
||||
@@ -2147,6 +2169,10 @@ ui:
|
||||
primary_color:
|
||||
label: Основной цвет
|
||||
text: Измените цвета, используемые в ваших темах
|
||||
layout:
|
||||
label: Layout
|
||||
full_width: Full-width
|
||||
fixed_width: Fixed-width
|
||||
css_and_html:
|
||||
page_title: CSS и HTML
|
||||
custom_css:
|
||||
@@ -2251,6 +2277,70 @@ ui:
|
||||
show_logs: Show logs
|
||||
status: Status
|
||||
title: Badges
|
||||
apikeys:
|
||||
title: API Keys
|
||||
add_api_key: Add API Key
|
||||
desc: Description
|
||||
scope: Scope
|
||||
key: Key
|
||||
created: Created
|
||||
last_used: Last used
|
||||
add_or_edit_modal:
|
||||
add_title: Add API Key
|
||||
edit_title: Edit API Key
|
||||
description: Description
|
||||
description_required: Description is required.
|
||||
scope: Scope
|
||||
global: Global
|
||||
read-only: Read-only
|
||||
created_modal:
|
||||
title: API key created
|
||||
api_key: API key
|
||||
description: This key will not be displayed again. Make sure you take a copy before continuing.
|
||||
delete_modal:
|
||||
title: Delete API Key
|
||||
content: Any applications or scripts using this key will no longer be able to access the API. This is permanent!
|
||||
ai_settings:
|
||||
enabled:
|
||||
label: AI enabled
|
||||
check: Enable AI features
|
||||
text: The AI model must be configured correctly before it can be used.
|
||||
provider:
|
||||
label: Provider
|
||||
api_host:
|
||||
label: API host
|
||||
msg: API host is required
|
||||
api_key:
|
||||
label: API key
|
||||
check: Check
|
||||
check_success: "Connection successful."
|
||||
msg: API key is required
|
||||
model:
|
||||
label: Model
|
||||
msg: Model is required
|
||||
add_success: AI settings updated successfully.
|
||||
conversations:
|
||||
topic: Topic
|
||||
helpful: Helpful
|
||||
unhelpful: Unhelpful
|
||||
created: Created
|
||||
action: Action
|
||||
empty: No conversations found.
|
||||
delete_modal:
|
||||
title: Delete conversation
|
||||
content: Are you sure you want to delete this conversation? This is permanent!
|
||||
delete_success: Conversation deleted successfully.
|
||||
mcp:
|
||||
mcp_server:
|
||||
label: MCP server
|
||||
switch: Enabled
|
||||
type:
|
||||
label: Type
|
||||
url:
|
||||
label: URL
|
||||
http_header:
|
||||
label: HTTP header
|
||||
text: Please replace {key} with the API Key.
|
||||
form:
|
||||
optional: (опционально)
|
||||
empty: не может быть пустым
|
||||
@@ -2347,6 +2437,7 @@ ui:
|
||||
user_normal: This user is already normal.
|
||||
user_suspended: This user has been suspended.
|
||||
user_deleted: This user has been deleted.
|
||||
user_added: User has been added successfully.
|
||||
badge_activated: This badge has been activated.
|
||||
badge_inactivated: This badge has been inactivated.
|
||||
users_deleted: These users have been deleted.
|
||||
|
||||
+95
-4
@@ -234,7 +234,7 @@ backend:
|
||||
other: Žiadne povolenie na aktualizáciu.
|
||||
content_cannot_empty:
|
||||
other: Content cannot be empty.
|
||||
content_less_than_minumum:
|
||||
content_less_than_minimum:
|
||||
other: Not enough content entered.
|
||||
rank:
|
||||
fail_to_meet_the_condition:
|
||||
@@ -846,6 +846,16 @@ ui:
|
||||
http_403: HTTP Error 403
|
||||
logout: Log Out
|
||||
posts: Posts
|
||||
ai_assistant: AI Assistant
|
||||
ai_assistant:
|
||||
description: Got a question? Ask it and get answers, perspectives, and recommendations.
|
||||
recent_conversations: Recent Conversations
|
||||
show_more: Show more
|
||||
new: New chat
|
||||
ai_generate: AI-generated from posts and may not be accurate.
|
||||
copy: Copy
|
||||
ask_a_follow_up: Ask a follow-up
|
||||
ask_placeholder: Ask a question
|
||||
notifications:
|
||||
title: Oznámenia
|
||||
inbox: Doručená pošta
|
||||
@@ -1190,8 +1200,7 @@ ui:
|
||||
search:
|
||||
placeholder: Vyhľadávanie
|
||||
footer:
|
||||
build_on: >-
|
||||
Powered by <1> Apache Answer </1>- the open-source software that powers Q&A communities.<br />Made with love © {{cc}}.
|
||||
build_on: Powered by <1> Apache Answer </1>
|
||||
upload_img:
|
||||
name: Zmena
|
||||
loading: načítavanie...
|
||||
@@ -1768,6 +1777,7 @@ ui:
|
||||
branding: Budovanie značky
|
||||
legal: legálne
|
||||
write: písať
|
||||
terms: Terms
|
||||
tos: Podmienky služby
|
||||
privacy: Súkromie
|
||||
seo: SEO
|
||||
@@ -1778,6 +1788,18 @@ ui:
|
||||
plugins: Plugins
|
||||
installed_plugins: Installed Plugins
|
||||
apperance: Appearance
|
||||
community: Community
|
||||
advanced: Advanced
|
||||
tags: Tags
|
||||
rules: Rules
|
||||
policies: Policies
|
||||
security: Security
|
||||
files: Files
|
||||
apikeys: API Keys
|
||||
intelligence: Intelligence
|
||||
ai_assistant: AI Assistant
|
||||
ai_settings: AI Settings
|
||||
mcp: MCP
|
||||
website_welcome: Welcome to {{site_name}}
|
||||
user_center:
|
||||
login: Login
|
||||
@@ -2089,7 +2111,7 @@ ui:
|
||||
always_display: Always display external content
|
||||
ask_before_display: Ask before displaying external content
|
||||
write:
|
||||
page_title: Písať
|
||||
page_title: Files
|
||||
min_content:
|
||||
label: Minimum question body length
|
||||
text: Minimum allowed question body length in characters.
|
||||
@@ -2147,6 +2169,10 @@ ui:
|
||||
primary_color:
|
||||
label: Primary color
|
||||
text: Upraviť farby používané vašími motívmi
|
||||
layout:
|
||||
label: Layout
|
||||
full_width: Full-width
|
||||
fixed_width: Fixed-width
|
||||
css_and_html:
|
||||
page_title: CSS a HTML
|
||||
custom_css:
|
||||
@@ -2251,6 +2277,70 @@ ui:
|
||||
show_logs: Show logs
|
||||
status: Status
|
||||
title: Badges
|
||||
apikeys:
|
||||
title: API Keys
|
||||
add_api_key: Add API Key
|
||||
desc: Description
|
||||
scope: Scope
|
||||
key: Key
|
||||
created: Created
|
||||
last_used: Last used
|
||||
add_or_edit_modal:
|
||||
add_title: Add API Key
|
||||
edit_title: Edit API Key
|
||||
description: Description
|
||||
description_required: Description is required.
|
||||
scope: Scope
|
||||
global: Global
|
||||
read-only: Read-only
|
||||
created_modal:
|
||||
title: API key created
|
||||
api_key: API key
|
||||
description: This key will not be displayed again. Make sure you take a copy before continuing.
|
||||
delete_modal:
|
||||
title: Delete API Key
|
||||
content: Any applications or scripts using this key will no longer be able to access the API. This is permanent!
|
||||
ai_settings:
|
||||
enabled:
|
||||
label: AI enabled
|
||||
check: Enable AI features
|
||||
text: The AI model must be configured correctly before it can be used.
|
||||
provider:
|
||||
label: Provider
|
||||
api_host:
|
||||
label: API host
|
||||
msg: API host is required
|
||||
api_key:
|
||||
label: API key
|
||||
check: Check
|
||||
check_success: "Connection successful."
|
||||
msg: API key is required
|
||||
model:
|
||||
label: Model
|
||||
msg: Model is required
|
||||
add_success: AI settings updated successfully.
|
||||
conversations:
|
||||
topic: Topic
|
||||
helpful: Helpful
|
||||
unhelpful: Unhelpful
|
||||
created: Created
|
||||
action: Action
|
||||
empty: No conversations found.
|
||||
delete_modal:
|
||||
title: Delete conversation
|
||||
content: Are you sure you want to delete this conversation? This is permanent!
|
||||
delete_success: Conversation deleted successfully.
|
||||
mcp:
|
||||
mcp_server:
|
||||
label: MCP server
|
||||
switch: Enabled
|
||||
type:
|
||||
label: Type
|
||||
url:
|
||||
label: URL
|
||||
http_header:
|
||||
label: HTTP header
|
||||
text: Please replace {key} with the API Key.
|
||||
form:
|
||||
optional: (voliteľné)
|
||||
empty: nemôže byť prázdne
|
||||
@@ -2347,6 +2437,7 @@ ui:
|
||||
user_normal: This user is already normal.
|
||||
user_suspended: This user has been suspended.
|
||||
user_deleted: This user has been deleted.
|
||||
user_added: User has been added successfully.
|
||||
badge_activated: This badge has been activated.
|
||||
badge_inactivated: This badge has been inactivated.
|
||||
users_deleted: These users have been deleted.
|
||||
|
||||
+95
-4
@@ -234,7 +234,7 @@ backend:
|
||||
other: No permission to update.
|
||||
content_cannot_empty:
|
||||
other: Content cannot be empty.
|
||||
content_less_than_minumum:
|
||||
content_less_than_minimum:
|
||||
other: Not enough content entered.
|
||||
rank:
|
||||
fail_to_meet_the_condition:
|
||||
@@ -846,6 +846,16 @@ ui:
|
||||
http_403: HTTP Error 403
|
||||
logout: Logga ut
|
||||
posts: Posts
|
||||
ai_assistant: AI Assistant
|
||||
ai_assistant:
|
||||
description: Got a question? Ask it and get answers, perspectives, and recommendations.
|
||||
recent_conversations: Recent Conversations
|
||||
show_more: Show more
|
||||
new: New chat
|
||||
ai_generate: AI-generated from posts and may not be accurate.
|
||||
copy: Copy
|
||||
ask_a_follow_up: Ask a follow-up
|
||||
ask_placeholder: Ask a question
|
||||
notifications:
|
||||
title: Notifications
|
||||
inbox: Inkorg
|
||||
@@ -1190,8 +1200,7 @@ ui:
|
||||
search:
|
||||
placeholder: Sök
|
||||
footer:
|
||||
build_on: >-
|
||||
Powered by <1> Apache Answer </1>- the open-source software that powers Q&A communities.<br />Made with love © {{cc}}.
|
||||
build_on: Powered by <1> Apache Answer </1>
|
||||
upload_img:
|
||||
name: Ändra
|
||||
loading: loading...
|
||||
@@ -1768,6 +1777,7 @@ ui:
|
||||
branding: Branding
|
||||
legal: Legal
|
||||
write: Write
|
||||
terms: Terms
|
||||
tos: Användarvillkor
|
||||
privacy: Privacy
|
||||
seo: SEO
|
||||
@@ -1778,6 +1788,18 @@ ui:
|
||||
plugins: Plugins
|
||||
installed_plugins: Installed Plugins
|
||||
apperance: Appearance
|
||||
community: Community
|
||||
advanced: Advanced
|
||||
tags: Tags
|
||||
rules: Rules
|
||||
policies: Policies
|
||||
security: Security
|
||||
files: Files
|
||||
apikeys: API Keys
|
||||
intelligence: Intelligence
|
||||
ai_assistant: AI Assistant
|
||||
ai_settings: AI Settings
|
||||
mcp: MCP
|
||||
website_welcome: Välkommen till {{site_name}}
|
||||
user_center:
|
||||
login: Login
|
||||
@@ -2089,7 +2111,7 @@ ui:
|
||||
always_display: Always display external content
|
||||
ask_before_display: Ask before displaying external content
|
||||
write:
|
||||
page_title: Write
|
||||
page_title: Files
|
||||
min_content:
|
||||
label: Minimum question body length
|
||||
text: Minimum allowed question body length in characters.
|
||||
@@ -2147,6 +2169,10 @@ ui:
|
||||
primary_color:
|
||||
label: Primary color
|
||||
text: Modify the colors used by your themes
|
||||
layout:
|
||||
label: Layout
|
||||
full_width: Full-width
|
||||
fixed_width: Fixed-width
|
||||
css_and_html:
|
||||
page_title: CSS och HTML
|
||||
custom_css:
|
||||
@@ -2251,6 +2277,70 @@ ui:
|
||||
show_logs: Visa loggar
|
||||
status: Status
|
||||
title: Badges
|
||||
apikeys:
|
||||
title: API Keys
|
||||
add_api_key: Add API Key
|
||||
desc: Description
|
||||
scope: Scope
|
||||
key: Key
|
||||
created: Created
|
||||
last_used: Last used
|
||||
add_or_edit_modal:
|
||||
add_title: Add API Key
|
||||
edit_title: Edit API Key
|
||||
description: Description
|
||||
description_required: Description is required.
|
||||
scope: Scope
|
||||
global: Global
|
||||
read-only: Read-only
|
||||
created_modal:
|
||||
title: API key created
|
||||
api_key: API key
|
||||
description: This key will not be displayed again. Make sure you take a copy before continuing.
|
||||
delete_modal:
|
||||
title: Delete API Key
|
||||
content: Any applications or scripts using this key will no longer be able to access the API. This is permanent!
|
||||
ai_settings:
|
||||
enabled:
|
||||
label: AI enabled
|
||||
check: Enable AI features
|
||||
text: The AI model must be configured correctly before it can be used.
|
||||
provider:
|
||||
label: Provider
|
||||
api_host:
|
||||
label: API host
|
||||
msg: API host is required
|
||||
api_key:
|
||||
label: API key
|
||||
check: Check
|
||||
check_success: "Connection successful."
|
||||
msg: API key is required
|
||||
model:
|
||||
label: Model
|
||||
msg: Model is required
|
||||
add_success: AI settings updated successfully.
|
||||
conversations:
|
||||
topic: Topic
|
||||
helpful: Helpful
|
||||
unhelpful: Unhelpful
|
||||
created: Created
|
||||
action: Action
|
||||
empty: No conversations found.
|
||||
delete_modal:
|
||||
title: Delete conversation
|
||||
content: Are you sure you want to delete this conversation? This is permanent!
|
||||
delete_success: Conversation deleted successfully.
|
||||
mcp:
|
||||
mcp_server:
|
||||
label: MCP server
|
||||
switch: Enabled
|
||||
type:
|
||||
label: Type
|
||||
url:
|
||||
label: URL
|
||||
http_header:
|
||||
label: HTTP header
|
||||
text: Please replace {key} with the API Key.
|
||||
form:
|
||||
optional: (optional)
|
||||
empty: cannot be empty
|
||||
@@ -2347,6 +2437,7 @@ ui:
|
||||
user_normal: This user is already normal.
|
||||
user_suspended: This user has been suspended.
|
||||
user_deleted: This user has been deleted.
|
||||
user_added: User has been added successfully.
|
||||
badge_activated: This badge has been activated.
|
||||
badge_inactivated: This badge has been inactivated.
|
||||
users_deleted: These users have been deleted.
|
||||
|
||||
+95
-4
@@ -234,7 +234,7 @@ backend:
|
||||
other: No permission to update.
|
||||
content_cannot_empty:
|
||||
other: Content cannot be empty.
|
||||
content_less_than_minumum:
|
||||
content_less_than_minimum:
|
||||
other: Not enough content entered.
|
||||
rank:
|
||||
fail_to_meet_the_condition:
|
||||
@@ -846,6 +846,16 @@ ui:
|
||||
http_403: HTTP Error 403
|
||||
logout: Log Out
|
||||
posts: Posts
|
||||
ai_assistant: AI Assistant
|
||||
ai_assistant:
|
||||
description: Got a question? Ask it and get answers, perspectives, and recommendations.
|
||||
recent_conversations: Recent Conversations
|
||||
show_more: Show more
|
||||
new: New chat
|
||||
ai_generate: AI-generated from posts and may not be accurate.
|
||||
copy: Copy
|
||||
ask_a_follow_up: Ask a follow-up
|
||||
ask_placeholder: Ask a question
|
||||
notifications:
|
||||
title: నోటిఫికేషన్లు
|
||||
inbox: ఇన్బాక్స్
|
||||
@@ -1190,8 +1200,7 @@ ui:
|
||||
search:
|
||||
placeholder: Search
|
||||
footer:
|
||||
build_on: >-
|
||||
Powered by <1> Apache Answer </1>- the open-source software that powers Q&A communities.<br />Made with love © {{cc}}.
|
||||
build_on: Powered by <1> Apache Answer </1>
|
||||
upload_img:
|
||||
name: Change
|
||||
loading: loading...
|
||||
@@ -1768,6 +1777,7 @@ ui:
|
||||
branding: Branding
|
||||
legal: Legal
|
||||
write: Write
|
||||
terms: Terms
|
||||
tos: Terms of Service
|
||||
privacy: Privacy
|
||||
seo: SEO
|
||||
@@ -1778,6 +1788,18 @@ ui:
|
||||
plugins: Plugins
|
||||
installed_plugins: Installed Plugins
|
||||
apperance: Appearance
|
||||
community: Community
|
||||
advanced: Advanced
|
||||
tags: Tags
|
||||
rules: Rules
|
||||
policies: Policies
|
||||
security: Security
|
||||
files: Files
|
||||
apikeys: API Keys
|
||||
intelligence: Intelligence
|
||||
ai_assistant: AI Assistant
|
||||
ai_settings: AI Settings
|
||||
mcp: MCP
|
||||
website_welcome: Welcome to {{site_name}}
|
||||
user_center:
|
||||
login: Login
|
||||
@@ -2089,7 +2111,7 @@ ui:
|
||||
always_display: Always display external content
|
||||
ask_before_display: Ask before displaying external content
|
||||
write:
|
||||
page_title: Write
|
||||
page_title: Files
|
||||
min_content:
|
||||
label: Minimum question body length
|
||||
text: Minimum allowed question body length in characters.
|
||||
@@ -2147,6 +2169,10 @@ ui:
|
||||
primary_color:
|
||||
label: Primary color
|
||||
text: Modify the colors used by your themes
|
||||
layout:
|
||||
label: Layout
|
||||
full_width: Full-width
|
||||
fixed_width: Fixed-width
|
||||
css_and_html:
|
||||
page_title: CSS and HTML
|
||||
custom_css:
|
||||
@@ -2251,6 +2277,70 @@ ui:
|
||||
show_logs: Show logs
|
||||
status: Status
|
||||
title: Badges
|
||||
apikeys:
|
||||
title: API Keys
|
||||
add_api_key: Add API Key
|
||||
desc: Description
|
||||
scope: Scope
|
||||
key: Key
|
||||
created: Created
|
||||
last_used: Last used
|
||||
add_or_edit_modal:
|
||||
add_title: Add API Key
|
||||
edit_title: Edit API Key
|
||||
description: Description
|
||||
description_required: Description is required.
|
||||
scope: Scope
|
||||
global: Global
|
||||
read-only: Read-only
|
||||
created_modal:
|
||||
title: API key created
|
||||
api_key: API key
|
||||
description: This key will not be displayed again. Make sure you take a copy before continuing.
|
||||
delete_modal:
|
||||
title: Delete API Key
|
||||
content: Any applications or scripts using this key will no longer be able to access the API. This is permanent!
|
||||
ai_settings:
|
||||
enabled:
|
||||
label: AI enabled
|
||||
check: Enable AI features
|
||||
text: The AI model must be configured correctly before it can be used.
|
||||
provider:
|
||||
label: Provider
|
||||
api_host:
|
||||
label: API host
|
||||
msg: API host is required
|
||||
api_key:
|
||||
label: API key
|
||||
check: Check
|
||||
check_success: "Connection successful."
|
||||
msg: API key is required
|
||||
model:
|
||||
label: Model
|
||||
msg: Model is required
|
||||
add_success: AI settings updated successfully.
|
||||
conversations:
|
||||
topic: Topic
|
||||
helpful: Helpful
|
||||
unhelpful: Unhelpful
|
||||
created: Created
|
||||
action: Action
|
||||
empty: No conversations found.
|
||||
delete_modal:
|
||||
title: Delete conversation
|
||||
content: Are you sure you want to delete this conversation? This is permanent!
|
||||
delete_success: Conversation deleted successfully.
|
||||
mcp:
|
||||
mcp_server:
|
||||
label: MCP server
|
||||
switch: Enabled
|
||||
type:
|
||||
label: Type
|
||||
url:
|
||||
label: URL
|
||||
http_header:
|
||||
label: HTTP header
|
||||
text: Please replace {key} with the API Key.
|
||||
form:
|
||||
optional: (optional)
|
||||
empty: cannot be empty
|
||||
@@ -2347,6 +2437,7 @@ ui:
|
||||
user_normal: This user is already normal.
|
||||
user_suspended: This user has been suspended.
|
||||
user_deleted: This user has been deleted.
|
||||
user_added: User has been added successfully.
|
||||
badge_activated: This badge has been activated.
|
||||
badge_inactivated: This badge has been inactivated.
|
||||
users_deleted: These users have been deleted.
|
||||
|
||||
+116
-25
@@ -234,7 +234,7 @@ backend:
|
||||
other: Güncelleme izni yok.
|
||||
content_cannot_empty:
|
||||
other: İçerik boş olamaz.
|
||||
content_less_than_minumum:
|
||||
content_less_than_minimum:
|
||||
other: Not enough content entered.
|
||||
rank:
|
||||
fail_to_meet_the_condition:
|
||||
@@ -266,7 +266,7 @@ backend:
|
||||
cannot_set_synonym_as_itself:
|
||||
other: Bir etiketin eş anlamlısını kendisi olarak ayarlayamazsınız.
|
||||
minimum_count:
|
||||
other: Not enough tags were entered.
|
||||
other: Yeterli etiket girilmedi.
|
||||
smtp:
|
||||
config_from_name_cannot_be_email:
|
||||
other: Gönderen adı bir e-posta adresi olamaz.
|
||||
@@ -311,13 +311,13 @@ backend:
|
||||
add_bulk_users_amount_error:
|
||||
other: "Bir kerede eklediğiniz kullanıcı sayısı 1-{{.MaxAmount}} aralığında olmalıdır."
|
||||
status_suspended_forever:
|
||||
other: "<strong>This user was suspended forever.</strong> This user doesn't meet a community guideline."
|
||||
other: "<strong>Bu kullanıcı kalıcı olarak uzaklaştırıldı.</strong> Bu kullanıcı topluluk yönergelerine uymuyor."
|
||||
status_suspended_until:
|
||||
other: "<strong>This user was suspended until {{.SuspendedUntil}}.</strong> This user doesn't meet a community guideline."
|
||||
other: "<strong>Bu kullanıcı {{.SuspendedUntil}} tarihine kadar askıya alındı.</strong> Bu kullanıcı topluluk yönergelerine uymuyor."
|
||||
status_deleted:
|
||||
other: "This user was deleted."
|
||||
other: "Bu kullanıcı silindi."
|
||||
status_inactive:
|
||||
other: "This user is inactive."
|
||||
other: "Bu kullanıcı aktif değil."
|
||||
config:
|
||||
read_config_failed:
|
||||
other: Yapılandırma okunamadı.
|
||||
@@ -821,7 +821,7 @@ ui:
|
||||
tag_wiki: etiket wikisi
|
||||
create_tag: Etiket Oluştur
|
||||
edit_tag: Etiketi Düzenle
|
||||
ask_a_question: Create Question
|
||||
ask_a_question: Soru Oluştur
|
||||
edit_question: Soruyu Düzenle
|
||||
edit_answer: Cevabı Düzenle
|
||||
search: Ara
|
||||
@@ -845,7 +845,17 @@ ui:
|
||||
http_50X: HTTP Hatası 500
|
||||
http_403: HTTP Hatası 403
|
||||
logout: Çıkış Yap
|
||||
posts: Posts
|
||||
posts: Gönderiler
|
||||
ai_assistant: AI Assistant
|
||||
ai_assistant:
|
||||
description: Got a question? Ask it and get answers, perspectives, and recommendations.
|
||||
recent_conversations: Recent Conversations
|
||||
show_more: Show more
|
||||
new: New chat
|
||||
ai_generate: AI-generated from posts and may not be accurate.
|
||||
copy: Copy
|
||||
ask_a_follow_up: Ask a follow-up
|
||||
ask_placeholder: Ask a question
|
||||
notifications:
|
||||
title: Bildirimler
|
||||
inbox: Gelen Kutusu
|
||||
@@ -1069,9 +1079,9 @@ ui:
|
||||
day: gün
|
||||
hours: saatler
|
||||
days: günler
|
||||
month: month
|
||||
months: months
|
||||
year: year
|
||||
month: ay
|
||||
months: aylar
|
||||
year: yıl
|
||||
reaction:
|
||||
heart: kalp
|
||||
smile: gülümseme
|
||||
@@ -1127,10 +1137,10 @@ ui:
|
||||
more: Daha Fazla
|
||||
wiki: Wiki
|
||||
ask:
|
||||
title: Create Question
|
||||
title: Soru Oluştur
|
||||
edit_title: Soruyu Düzenle
|
||||
default_reason: Soruyu düzenle
|
||||
default_first_reason: Create question
|
||||
default_first_reason: Soru oluştur
|
||||
similar_questions: Benzer sorular
|
||||
form:
|
||||
fields:
|
||||
@@ -1138,7 +1148,7 @@ ui:
|
||||
label: Revizyon
|
||||
title:
|
||||
label: Başlık
|
||||
placeholder: What's your topic? Be specific.
|
||||
placeholder: Konu nedir? Ayrıntılı yaz.
|
||||
msg:
|
||||
empty: Başlık boş olamaz.
|
||||
range: Başlık en fazla 150 karakter olabilir
|
||||
@@ -1147,8 +1157,8 @@ ui:
|
||||
msg:
|
||||
empty: İçerik boş olamaz.
|
||||
hint:
|
||||
optional_body: Describe what the question is about.
|
||||
minimum_characters: "Describe what the question is about, at least {{min_content_length}} characters are required."
|
||||
optional_body: Sorunun neyle ilgili olduğunu açıklayın.
|
||||
minimum_characters: "Sorunun neyle ilgili olduğunu açıklayın, en az {{min_content_length}} karakter gereklidir."
|
||||
tags:
|
||||
label: Etiketler
|
||||
msg:
|
||||
@@ -1169,9 +1179,9 @@ ui:
|
||||
add_btn: Etiket ekle
|
||||
create_btn: Yeni etiket oluştur
|
||||
search_tag: Etiket ara
|
||||
hint: Describe what your content is about, at least one tag is required.
|
||||
hint_zero_tags: Describe what your content is about.
|
||||
hint_more_than_one_tag: "Describe what your content is about, at least {{min_tags_number}} tags are required."
|
||||
hint: Sorunuzun ne hakkında olduğunu tanımlayın, en az bir etiket gereklidir.
|
||||
hint_zero_tags: İçeriğinizin neyle ilgili olduğunu açıklayın.
|
||||
hint_more_than_one_tag: "İçeriğinizin neyle ilgili olduğunu açıklayın, en az {{min_tags_number}} etiket gereklidir."
|
||||
no_result: Eşleşen etiket bulunamadı
|
||||
tag_required_text: Gerekli etiket (en az bir tane)
|
||||
header:
|
||||
@@ -1190,8 +1200,7 @@ ui:
|
||||
search:
|
||||
placeholder: Ara
|
||||
footer:
|
||||
build_on: >-
|
||||
<1>Apache Answer</1> tarafından desteklenmektedir - S&C topluluklarına güç veren açık kaynaklı yazılım.<br />Sevgiyle yapıldı © {{cc}}.
|
||||
build_on: Powered by <1> Apache Answer </1>
|
||||
upload_img:
|
||||
name: Değiştir
|
||||
loading: yükleniyor...
|
||||
@@ -1224,7 +1233,7 @@ ui:
|
||||
msg:
|
||||
empty: İsim boş olamaz.
|
||||
range: İsim 2 ile 30 karakter arasında olmalıdır.
|
||||
character: 'Must use the character set "a-z", "0-9", " - . _"'
|
||||
character: 'Yalnızca "a-z", "0-9", " - . _" karakterleri kullanılabilir'
|
||||
email:
|
||||
label: E-posta
|
||||
msg:
|
||||
@@ -1377,10 +1386,10 @@ ui:
|
||||
review: Revizyonunuz incelendikten sonra görünecek.
|
||||
sent_success: Başarıyla gönderildi
|
||||
related_question:
|
||||
title: Related
|
||||
title: İle ilgili
|
||||
answers: cevap
|
||||
linked_question:
|
||||
title: Linked
|
||||
title: Bağlantılı
|
||||
description: Posts linked to
|
||||
no_linked_question: No contents linked from this content.
|
||||
invite_to_answer:
|
||||
@@ -1768,6 +1777,7 @@ ui:
|
||||
branding: Marka
|
||||
legal: Yasal
|
||||
write: Yaz
|
||||
terms: Terms
|
||||
tos: Kullanım Şartları
|
||||
privacy: Gizlilik
|
||||
seo: SEO
|
||||
@@ -1778,6 +1788,18 @@ ui:
|
||||
plugins: Eklentiler
|
||||
installed_plugins: Kurulu Eklentiler
|
||||
apperance: Görünüm
|
||||
community: Community
|
||||
advanced: Advanced
|
||||
tags: Tags
|
||||
rules: Rules
|
||||
policies: Policies
|
||||
security: Security
|
||||
files: Files
|
||||
apikeys: API Keys
|
||||
intelligence: Intelligence
|
||||
ai_assistant: AI Assistant
|
||||
ai_settings: AI Settings
|
||||
mcp: MCP
|
||||
website_welcome: '{{site_name}} sitesine hoş geldiniz'
|
||||
user_center:
|
||||
login: Giriş
|
||||
@@ -2089,7 +2111,7 @@ ui:
|
||||
always_display: Her zaman harici içeriği göster
|
||||
ask_before_display: Harici içeriği göstermeden önce sor
|
||||
write:
|
||||
page_title: Yazma
|
||||
page_title: Files
|
||||
min_content:
|
||||
label: Minimum question body length
|
||||
text: Minimum allowed question body length in characters.
|
||||
@@ -2147,6 +2169,10 @@ ui:
|
||||
primary_color:
|
||||
label: Ana renk
|
||||
text: Temalarınızda kullanılan renkleri değiştirin
|
||||
layout:
|
||||
label: Layout
|
||||
full_width: Full-width
|
||||
fixed_width: Fixed-width
|
||||
css_and_html:
|
||||
page_title: CSS ve HTML
|
||||
custom_css:
|
||||
@@ -2251,6 +2277,70 @@ ui:
|
||||
show_logs: Kayıtları göster
|
||||
status: Durum
|
||||
title: Rozetler
|
||||
apikeys:
|
||||
title: API Keys
|
||||
add_api_key: Add API Key
|
||||
desc: Description
|
||||
scope: Scope
|
||||
key: Key
|
||||
created: Created
|
||||
last_used: Last used
|
||||
add_or_edit_modal:
|
||||
add_title: Add API Key
|
||||
edit_title: Edit API Key
|
||||
description: Description
|
||||
description_required: Description is required.
|
||||
scope: Scope
|
||||
global: Global
|
||||
read-only: Read-only
|
||||
created_modal:
|
||||
title: API key created
|
||||
api_key: API key
|
||||
description: This key will not be displayed again. Make sure you take a copy before continuing.
|
||||
delete_modal:
|
||||
title: Delete API Key
|
||||
content: Any applications or scripts using this key will no longer be able to access the API. This is permanent!
|
||||
ai_settings:
|
||||
enabled:
|
||||
label: AI enabled
|
||||
check: Enable AI features
|
||||
text: The AI model must be configured correctly before it can be used.
|
||||
provider:
|
||||
label: Provider
|
||||
api_host:
|
||||
label: API host
|
||||
msg: API host is required
|
||||
api_key:
|
||||
label: API key
|
||||
check: Check
|
||||
check_success: "Connection successful."
|
||||
msg: API key is required
|
||||
model:
|
||||
label: Model
|
||||
msg: Model is required
|
||||
add_success: AI settings updated successfully.
|
||||
conversations:
|
||||
topic: Topic
|
||||
helpful: Helpful
|
||||
unhelpful: Unhelpful
|
||||
created: Created
|
||||
action: Action
|
||||
empty: No conversations found.
|
||||
delete_modal:
|
||||
title: Delete conversation
|
||||
content: Are you sure you want to delete this conversation? This is permanent!
|
||||
delete_success: Conversation deleted successfully.
|
||||
mcp:
|
||||
mcp_server:
|
||||
label: MCP server
|
||||
switch: Enabled
|
||||
type:
|
||||
label: Type
|
||||
url:
|
||||
label: URL
|
||||
http_header:
|
||||
label: HTTP header
|
||||
text: Please replace {key} with the API Key.
|
||||
form:
|
||||
optional: (isteğe bağlı)
|
||||
empty: boş olamaz
|
||||
@@ -2347,6 +2437,7 @@ ui:
|
||||
user_normal: Bu kullanıcı zaten normal durumda.
|
||||
user_suspended: Bu kullanıcı askıya alındı.
|
||||
user_deleted: Bu kullanıcı silindi.
|
||||
user_added: User has been added successfully.
|
||||
badge_activated: Bu rozet etkinleştirildi.
|
||||
badge_inactivated: Bu rozet devre dışı bırakıldı.
|
||||
users_deleted: Bu kullanıcılar silindi.
|
||||
|
||||
+95
-4
@@ -234,7 +234,7 @@ backend:
|
||||
other: Немає дозволу на оновлення.
|
||||
content_cannot_empty:
|
||||
other: Content cannot be empty.
|
||||
content_less_than_minumum:
|
||||
content_less_than_minimum:
|
||||
other: Not enough content entered.
|
||||
rank:
|
||||
fail_to_meet_the_condition:
|
||||
@@ -846,6 +846,16 @@ ui:
|
||||
http_403: Помилка HTTP 403
|
||||
logout: Вийти
|
||||
posts: Posts
|
||||
ai_assistant: AI Assistant
|
||||
ai_assistant:
|
||||
description: Got a question? Ask it and get answers, perspectives, and recommendations.
|
||||
recent_conversations: Recent Conversations
|
||||
show_more: Show more
|
||||
new: New chat
|
||||
ai_generate: AI-generated from posts and may not be accurate.
|
||||
copy: Copy
|
||||
ask_a_follow_up: Ask a follow-up
|
||||
ask_placeholder: Ask a question
|
||||
notifications:
|
||||
title: Сповіщення
|
||||
inbox: Вхідні
|
||||
@@ -1190,8 +1200,7 @@ ui:
|
||||
search:
|
||||
placeholder: Пошук
|
||||
footer:
|
||||
build_on: >-
|
||||
Працює на основі <1> Apache Answer </1> - програмного забезпечення з відкритим вихідним кодом, яке забезпечує роботу спільнот запитань та відповідей.<br />Зроблено з любов'ю © {{cc}}.
|
||||
build_on: Powered by <1> Apache Answer </1>
|
||||
upload_img:
|
||||
name: Змінити
|
||||
loading: завантаження...
|
||||
@@ -1768,6 +1777,7 @@ ui:
|
||||
branding: Брендинг
|
||||
legal: Правила та умови
|
||||
write: Написати
|
||||
terms: Terms
|
||||
tos: Умови використання
|
||||
privacy: Приватність
|
||||
seo: SEO
|
||||
@@ -1778,6 +1788,18 @@ ui:
|
||||
plugins: Плагіни
|
||||
installed_plugins: Встановлені плагіни
|
||||
apperance: Appearance
|
||||
community: Community
|
||||
advanced: Advanced
|
||||
tags: Tags
|
||||
rules: Rules
|
||||
policies: Policies
|
||||
security: Security
|
||||
files: Files
|
||||
apikeys: API Keys
|
||||
intelligence: Intelligence
|
||||
ai_assistant: AI Assistant
|
||||
ai_settings: AI Settings
|
||||
mcp: MCP
|
||||
website_welcome: Ласкаво просимо до {{site_name}}
|
||||
user_center:
|
||||
login: Вхід
|
||||
@@ -2089,7 +2111,7 @@ ui:
|
||||
always_display: Always display external content
|
||||
ask_before_display: Ask before displaying external content
|
||||
write:
|
||||
page_title: Написати
|
||||
page_title: Files
|
||||
min_content:
|
||||
label: Minimum question body length
|
||||
text: Minimum allowed question body length in characters.
|
||||
@@ -2147,6 +2169,10 @@ ui:
|
||||
primary_color:
|
||||
label: Основний колір
|
||||
text: Змінюйте кольори, що використовуються у ваших темах
|
||||
layout:
|
||||
label: Layout
|
||||
full_width: Full-width
|
||||
fixed_width: Fixed-width
|
||||
css_and_html:
|
||||
page_title: CSS та HTML
|
||||
custom_css:
|
||||
@@ -2251,6 +2277,70 @@ ui:
|
||||
show_logs: Показати записи журналу
|
||||
status: Статус
|
||||
title: Значки
|
||||
apikeys:
|
||||
title: API Keys
|
||||
add_api_key: Add API Key
|
||||
desc: Description
|
||||
scope: Scope
|
||||
key: Key
|
||||
created: Created
|
||||
last_used: Last used
|
||||
add_or_edit_modal:
|
||||
add_title: Add API Key
|
||||
edit_title: Edit API Key
|
||||
description: Description
|
||||
description_required: Description is required.
|
||||
scope: Scope
|
||||
global: Global
|
||||
read-only: Read-only
|
||||
created_modal:
|
||||
title: API key created
|
||||
api_key: API key
|
||||
description: This key will not be displayed again. Make sure you take a copy before continuing.
|
||||
delete_modal:
|
||||
title: Delete API Key
|
||||
content: Any applications or scripts using this key will no longer be able to access the API. This is permanent!
|
||||
ai_settings:
|
||||
enabled:
|
||||
label: AI enabled
|
||||
check: Enable AI features
|
||||
text: The AI model must be configured correctly before it can be used.
|
||||
provider:
|
||||
label: Provider
|
||||
api_host:
|
||||
label: API host
|
||||
msg: API host is required
|
||||
api_key:
|
||||
label: API key
|
||||
check: Check
|
||||
check_success: "Connection successful."
|
||||
msg: API key is required
|
||||
model:
|
||||
label: Model
|
||||
msg: Model is required
|
||||
add_success: AI settings updated successfully.
|
||||
conversations:
|
||||
topic: Topic
|
||||
helpful: Helpful
|
||||
unhelpful: Unhelpful
|
||||
created: Created
|
||||
action: Action
|
||||
empty: No conversations found.
|
||||
delete_modal:
|
||||
title: Delete conversation
|
||||
content: Are you sure you want to delete this conversation? This is permanent!
|
||||
delete_success: Conversation deleted successfully.
|
||||
mcp:
|
||||
mcp_server:
|
||||
label: MCP server
|
||||
switch: Enabled
|
||||
type:
|
||||
label: Type
|
||||
url:
|
||||
label: URL
|
||||
http_header:
|
||||
label: HTTP header
|
||||
text: Please replace {key} with the API Key.
|
||||
form:
|
||||
optional: (необов'язково)
|
||||
empty: не може бути порожнім
|
||||
@@ -2347,6 +2437,7 @@ ui:
|
||||
user_normal: Цей користувач вже нормальний.
|
||||
user_suspended: Цього користувача було відсторонено.
|
||||
user_deleted: Цього користувача було видалено.
|
||||
user_added: User has been added successfully.
|
||||
badge_activated: Цей бейдж було активовано.
|
||||
badge_inactivated: Цей бейдж було деактивовано.
|
||||
users_deleted: These users have been deleted.
|
||||
|
||||
+95
-4
@@ -234,7 +234,7 @@ backend:
|
||||
other: Không có quyền cập nhật.
|
||||
content_cannot_empty:
|
||||
other: Content cannot be empty.
|
||||
content_less_than_minumum:
|
||||
content_less_than_minimum:
|
||||
other: Not enough content entered.
|
||||
rank:
|
||||
fail_to_meet_the_condition:
|
||||
@@ -846,6 +846,16 @@ ui:
|
||||
http_403: Lỗi HTTP 403
|
||||
logout: Đăng xuất
|
||||
posts: Posts
|
||||
ai_assistant: AI Assistant
|
||||
ai_assistant:
|
||||
description: Got a question? Ask it and get answers, perspectives, and recommendations.
|
||||
recent_conversations: Recent Conversations
|
||||
show_more: Show more
|
||||
new: New chat
|
||||
ai_generate: AI-generated from posts and may not be accurate.
|
||||
copy: Copy
|
||||
ask_a_follow_up: Ask a follow-up
|
||||
ask_placeholder: Ask a question
|
||||
notifications:
|
||||
title: Các thông báo
|
||||
inbox: Hộp thư đến
|
||||
@@ -1190,8 +1200,7 @@ ui:
|
||||
search:
|
||||
placeholder: Tìm kiếm
|
||||
footer:
|
||||
build_on: >-
|
||||
Được hỗ trợ bởi <1> Apache Answer </1>- phần mềm mã nguồn mở dành cho cộng đồng hỏi đáp.<br />Được tạo với tình yêu © {{cc}}.
|
||||
build_on: Powered by <1> Apache Answer </1>
|
||||
upload_img:
|
||||
name: Thay đổi
|
||||
loading: đang tải...
|
||||
@@ -1768,6 +1777,7 @@ ui:
|
||||
branding: Thương hiệu
|
||||
legal: Pháp lý
|
||||
write: Viết
|
||||
terms: Terms
|
||||
tos: Điều khoản dịch vụ
|
||||
privacy: Quyền riêng tư
|
||||
seo: SEO
|
||||
@@ -1778,6 +1788,18 @@ ui:
|
||||
plugins: Plugins
|
||||
installed_plugins: Plugin đã cài đặt
|
||||
apperance: Appearance
|
||||
community: Community
|
||||
advanced: Advanced
|
||||
tags: Tags
|
||||
rules: Rules
|
||||
policies: Policies
|
||||
security: Security
|
||||
files: Files
|
||||
apikeys: API Keys
|
||||
intelligence: Intelligence
|
||||
ai_assistant: AI Assistant
|
||||
ai_settings: AI Settings
|
||||
mcp: MCP
|
||||
website_welcome: Chào mừng bạn đến với {{site_name}}
|
||||
user_center:
|
||||
login: Đăng nhập
|
||||
@@ -2089,7 +2111,7 @@ ui:
|
||||
always_display: Always display external content
|
||||
ask_before_display: Ask before displaying external content
|
||||
write:
|
||||
page_title: Viết
|
||||
page_title: Files
|
||||
min_content:
|
||||
label: Minimum question body length
|
||||
text: Minimum allowed question body length in characters.
|
||||
@@ -2147,6 +2169,10 @@ ui:
|
||||
primary_color:
|
||||
label: Màu chính
|
||||
text: Thay đổi các màu sắc được sử dụng bởi chủ đề của bạn
|
||||
layout:
|
||||
label: Layout
|
||||
full_width: Full-width
|
||||
fixed_width: Fixed-width
|
||||
css_and_html:
|
||||
page_title: CSS và HTML
|
||||
custom_css:
|
||||
@@ -2251,6 +2277,70 @@ ui:
|
||||
show_logs: Hiển thị nhật ký
|
||||
status: Trạng thái
|
||||
title: Danh hiệu
|
||||
apikeys:
|
||||
title: API Keys
|
||||
add_api_key: Add API Key
|
||||
desc: Description
|
||||
scope: Scope
|
||||
key: Key
|
||||
created: Created
|
||||
last_used: Last used
|
||||
add_or_edit_modal:
|
||||
add_title: Add API Key
|
||||
edit_title: Edit API Key
|
||||
description: Description
|
||||
description_required: Description is required.
|
||||
scope: Scope
|
||||
global: Global
|
||||
read-only: Read-only
|
||||
created_modal:
|
||||
title: API key created
|
||||
api_key: API key
|
||||
description: This key will not be displayed again. Make sure you take a copy before continuing.
|
||||
delete_modal:
|
||||
title: Delete API Key
|
||||
content: Any applications or scripts using this key will no longer be able to access the API. This is permanent!
|
||||
ai_settings:
|
||||
enabled:
|
||||
label: AI enabled
|
||||
check: Enable AI features
|
||||
text: The AI model must be configured correctly before it can be used.
|
||||
provider:
|
||||
label: Provider
|
||||
api_host:
|
||||
label: API host
|
||||
msg: API host is required
|
||||
api_key:
|
||||
label: API key
|
||||
check: Check
|
||||
check_success: "Connection successful."
|
||||
msg: API key is required
|
||||
model:
|
||||
label: Model
|
||||
msg: Model is required
|
||||
add_success: AI settings updated successfully.
|
||||
conversations:
|
||||
topic: Topic
|
||||
helpful: Helpful
|
||||
unhelpful: Unhelpful
|
||||
created: Created
|
||||
action: Action
|
||||
empty: No conversations found.
|
||||
delete_modal:
|
||||
title: Delete conversation
|
||||
content: Are you sure you want to delete this conversation? This is permanent!
|
||||
delete_success: Conversation deleted successfully.
|
||||
mcp:
|
||||
mcp_server:
|
||||
label: MCP server
|
||||
switch: Enabled
|
||||
type:
|
||||
label: Type
|
||||
url:
|
||||
label: URL
|
||||
http_header:
|
||||
label: HTTP header
|
||||
text: Please replace {key} with the API Key.
|
||||
form:
|
||||
optional: (tùy chọn)
|
||||
empty: không thể trống
|
||||
@@ -2347,6 +2437,7 @@ ui:
|
||||
user_normal: Người dùng này đã bình thường.
|
||||
user_suspended: Người dùng này đã bị đình chỉ.
|
||||
user_deleted: Người dùng này đã bị xóa.
|
||||
user_added: User has been added successfully.
|
||||
badge_activated: Huy hiệu này đã được kích hoạt.
|
||||
badge_inactivated: Huy hiệu này đã bị vô hiệu hóa.
|
||||
users_deleted: These users have been deleted.
|
||||
|
||||
+95
-4
@@ -234,7 +234,7 @@ backend:
|
||||
other: 没有更新权限。
|
||||
content_cannot_empty:
|
||||
other: 内容不能为空。
|
||||
content_less_than_minumum:
|
||||
content_less_than_minimum:
|
||||
other: 输入的内容不足。
|
||||
rank:
|
||||
fail_to_meet_the_condition:
|
||||
@@ -846,6 +846,16 @@ ui:
|
||||
http_403: HTTP 错误 403
|
||||
logout: 退出
|
||||
posts: 帖子
|
||||
ai_assistant: AI 助手
|
||||
ai_assistant:
|
||||
description: 有问题?问它并获得答案、观点和建议。
|
||||
recent_conversations: 新对话
|
||||
show_more: 显示更多
|
||||
new: 新聊天
|
||||
ai_generate: 来自帖子的 AI,可能不准确。
|
||||
copy: 复制
|
||||
ask_a_follow_up: 提出后续问题
|
||||
ask_placeholder: 提问
|
||||
notifications:
|
||||
title: 通知
|
||||
inbox: 收件箱
|
||||
@@ -1190,8 +1200,7 @@ ui:
|
||||
search:
|
||||
placeholder: 搜索
|
||||
footer:
|
||||
build_on: >-
|
||||
由 <1>Apache Answer</1> 提供动力 - 驱动问答社区的开源软件。<br />用爱制造 © {{cc}}.
|
||||
build_on: Powered by <1> Apache Answer </1>
|
||||
upload_img:
|
||||
name: 更改
|
||||
loading: 加载中...
|
||||
@@ -1768,6 +1777,7 @@ ui:
|
||||
branding: 品牌
|
||||
legal: 法律条款
|
||||
write: 撰写
|
||||
terms: 服务条款
|
||||
tos: 服务条款
|
||||
privacy: 隐私政策
|
||||
seo: SEO
|
||||
@@ -1778,6 +1788,18 @@ ui:
|
||||
plugins: 插件
|
||||
installed_plugins: 已安装插件
|
||||
apperance: 外观
|
||||
community: 社区
|
||||
advanced: 高级选项
|
||||
tags: 标签
|
||||
rules: 规则
|
||||
policies: 政策
|
||||
security: 安全
|
||||
files: 文件
|
||||
apikeys: API 密钥
|
||||
intelligence: 智力
|
||||
ai_assistant: AI 助手
|
||||
ai_settings: AI 设置
|
||||
mcp: MCP
|
||||
website_welcome: 欢迎来到 {{site_name}}
|
||||
user_center:
|
||||
login: 登录
|
||||
@@ -2089,7 +2111,7 @@ ui:
|
||||
always_display: 总是显示外部内容
|
||||
ask_before_display: 在显示外部内容之前询问
|
||||
write:
|
||||
page_title: 编辑
|
||||
page_title: 文件
|
||||
min_content:
|
||||
label: 最小问题长度
|
||||
text: 最小允许的问题内容长度(字符)。
|
||||
@@ -2147,6 +2169,10 @@ ui:
|
||||
primary_color:
|
||||
label: 主色调
|
||||
text: 修改您主题使用的颜色
|
||||
layout:
|
||||
label: 布局
|
||||
full_width: 全宽度
|
||||
fixed_width: 固定宽度
|
||||
css_and_html:
|
||||
page_title: CSS 与 HTML
|
||||
custom_css:
|
||||
@@ -2251,6 +2277,70 @@ ui:
|
||||
show_logs: 显示日志
|
||||
status: 状态
|
||||
title: 徽章
|
||||
apikeys:
|
||||
title: API 密钥
|
||||
add_api_key: 添加 API 密钥
|
||||
desc: 描述
|
||||
scope: 范围
|
||||
key: 密钥
|
||||
created: 创建于
|
||||
last_used: 最后使用
|
||||
add_or_edit_modal:
|
||||
add_title: 添加 API 密钥
|
||||
edit_title: 编辑 API 密钥
|
||||
description: 描述
|
||||
description_required: 请输入描述。
|
||||
scope: 范围
|
||||
global: 全局
|
||||
read-only: 只读
|
||||
created_modal:
|
||||
title: 已创建API密钥
|
||||
api_key: API 密钥
|
||||
description: 此密钥将不会再次显示。请确保您在继续之前拿到一份副本。
|
||||
delete_modal:
|
||||
title: 删除API密钥
|
||||
content: 任何使用此密钥的应用程序或脚本都将无法访问API。这是永久性的!
|
||||
ai_settings:
|
||||
enabled:
|
||||
label: AI 已启用
|
||||
check: 启用AI功能
|
||||
text: AI 模型必须正确配置才能使用。
|
||||
provider:
|
||||
label: 提供商
|
||||
api_host:
|
||||
label: API 主机
|
||||
msg: API 主机是必需的
|
||||
api_key:
|
||||
label: API 密钥
|
||||
check: 检查
|
||||
check_success: "连接成功。"
|
||||
msg: API 密钥是必填项
|
||||
model:
|
||||
label: 模型
|
||||
msg: 模型是必需的
|
||||
add_success: AI 设置更新成功。
|
||||
conversations:
|
||||
topic: 主题
|
||||
helpful: 有帮助
|
||||
unhelpful: 没有帮助
|
||||
created: 创建于
|
||||
action: 操作
|
||||
empty: 没有找到会话
|
||||
delete_modal:
|
||||
title: 删除对话
|
||||
content: 您确定要删除此对话吗?这是永久性的!
|
||||
delete_success: 对话删除成功。
|
||||
mcp:
|
||||
mcp_server:
|
||||
label: MCP服务器
|
||||
switch: 已启用
|
||||
type:
|
||||
label: 类型
|
||||
url:
|
||||
label: 链接
|
||||
http_header:
|
||||
label: HTTP Header
|
||||
text: 请将 {key} 替换为 API 密钥。
|
||||
form:
|
||||
optional: (选填)
|
||||
empty: 不能为空
|
||||
@@ -2347,6 +2437,7 @@ ui:
|
||||
user_normal: 此用户已经是正常的。
|
||||
user_suspended: 此用户已被封禁。
|
||||
user_deleted: 此用户已被删除
|
||||
user_added: 用户添加成功。
|
||||
badge_activated: 此徽章已被激活。
|
||||
badge_inactivated: 此徽章已被禁用。
|
||||
users_deleted: 这些用户已被删除。
|
||||
|
||||
+95
-4
@@ -234,7 +234,7 @@ backend:
|
||||
other: 無更新權限。
|
||||
content_cannot_empty:
|
||||
other: Content cannot be empty.
|
||||
content_less_than_minumum:
|
||||
content_less_than_minimum:
|
||||
other: Not enough content entered.
|
||||
rank:
|
||||
fail_to_meet_the_condition:
|
||||
@@ -846,6 +846,16 @@ ui:
|
||||
http_403: HTTP 錯誤 403
|
||||
logout: 登出
|
||||
posts: Posts
|
||||
ai_assistant: AI Assistant
|
||||
ai_assistant:
|
||||
description: Got a question? Ask it and get answers, perspectives, and recommendations.
|
||||
recent_conversations: Recent Conversations
|
||||
show_more: Show more
|
||||
new: New chat
|
||||
ai_generate: AI-generated from posts and may not be accurate.
|
||||
copy: Copy
|
||||
ask_a_follow_up: Ask a follow-up
|
||||
ask_placeholder: Ask a question
|
||||
notifications:
|
||||
title: 通知
|
||||
inbox: 收件夾
|
||||
@@ -1190,8 +1200,7 @@ ui:
|
||||
search:
|
||||
placeholder: 搜尋
|
||||
footer:
|
||||
build_on: >-
|
||||
Powered by <1> Apache Answer </1>- the open-source software that powers Q&A communities.<br />Made with love © {{cc}}.
|
||||
build_on: Powered by <1> Apache Answer </1>
|
||||
upload_img:
|
||||
name: 更改
|
||||
loading: 讀取中...
|
||||
@@ -1768,6 +1777,7 @@ ui:
|
||||
branding: 品牌
|
||||
legal: 法律條款
|
||||
write: 撰寫
|
||||
terms: Terms
|
||||
tos: 服務條款
|
||||
privacy: 隱私政策
|
||||
seo: SEO
|
||||
@@ -1778,6 +1788,18 @@ ui:
|
||||
plugins: Plugins
|
||||
installed_plugins: Installed Plugins
|
||||
apperance: Appearance
|
||||
community: Community
|
||||
advanced: Advanced
|
||||
tags: Tags
|
||||
rules: Rules
|
||||
policies: Policies
|
||||
security: Security
|
||||
files: Files
|
||||
apikeys: API Keys
|
||||
intelligence: Intelligence
|
||||
ai_assistant: AI Assistant
|
||||
ai_settings: AI Settings
|
||||
mcp: MCP
|
||||
website_welcome: Welcome to {{site_name}}
|
||||
user_center:
|
||||
login: Login
|
||||
@@ -2089,7 +2111,7 @@ ui:
|
||||
always_display: Always display external content
|
||||
ask_before_display: Ask before displaying external content
|
||||
write:
|
||||
page_title: 編輯
|
||||
page_title: Files
|
||||
min_content:
|
||||
label: Minimum question body length
|
||||
text: Minimum allowed question body length in characters.
|
||||
@@ -2147,6 +2169,10 @@ ui:
|
||||
primary_color:
|
||||
label: 主色調
|
||||
text: 修改您主題使用的顏色
|
||||
layout:
|
||||
label: Layout
|
||||
full_width: Full-width
|
||||
fixed_width: Fixed-width
|
||||
css_and_html:
|
||||
page_title: CSS 與 HTML
|
||||
custom_css:
|
||||
@@ -2251,6 +2277,70 @@ ui:
|
||||
show_logs: Show logs
|
||||
status: Status
|
||||
title: Badges
|
||||
apikeys:
|
||||
title: API Keys
|
||||
add_api_key: Add API Key
|
||||
desc: Description
|
||||
scope: Scope
|
||||
key: Key
|
||||
created: Created
|
||||
last_used: Last used
|
||||
add_or_edit_modal:
|
||||
add_title: Add API Key
|
||||
edit_title: Edit API Key
|
||||
description: Description
|
||||
description_required: Description is required.
|
||||
scope: Scope
|
||||
global: Global
|
||||
read-only: Read-only
|
||||
created_modal:
|
||||
title: API key created
|
||||
api_key: API key
|
||||
description: This key will not be displayed again. Make sure you take a copy before continuing.
|
||||
delete_modal:
|
||||
title: Delete API Key
|
||||
content: Any applications or scripts using this key will no longer be able to access the API. This is permanent!
|
||||
ai_settings:
|
||||
enabled:
|
||||
label: AI enabled
|
||||
check: Enable AI features
|
||||
text: The AI model must be configured correctly before it can be used.
|
||||
provider:
|
||||
label: Provider
|
||||
api_host:
|
||||
label: API host
|
||||
msg: API host is required
|
||||
api_key:
|
||||
label: API key
|
||||
check: Check
|
||||
check_success: "Connection successful."
|
||||
msg: API key is required
|
||||
model:
|
||||
label: Model
|
||||
msg: Model is required
|
||||
add_success: AI settings updated successfully.
|
||||
conversations:
|
||||
topic: Topic
|
||||
helpful: Helpful
|
||||
unhelpful: Unhelpful
|
||||
created: Created
|
||||
action: Action
|
||||
empty: No conversations found.
|
||||
delete_modal:
|
||||
title: Delete conversation
|
||||
content: Are you sure you want to delete this conversation? This is permanent!
|
||||
delete_success: Conversation deleted successfully.
|
||||
mcp:
|
||||
mcp_server:
|
||||
label: MCP server
|
||||
switch: Enabled
|
||||
type:
|
||||
label: Type
|
||||
url:
|
||||
label: URL
|
||||
http_header:
|
||||
label: HTTP header
|
||||
text: Please replace {key} with the API Key.
|
||||
form:
|
||||
optional: (選填)
|
||||
empty: 不能為空
|
||||
@@ -2347,6 +2437,7 @@ ui:
|
||||
user_normal: This user is already normal.
|
||||
user_suspended: This user has been suspended.
|
||||
user_deleted: This user has been deleted.
|
||||
user_added: User has been added successfully.
|
||||
badge_activated: This badge has been activated.
|
||||
badge_inactivated: This badge has been inactivated.
|
||||
users_deleted: These users have been deleted.
|
||||
|
||||
@@ -117,7 +117,9 @@ func ReadConfig(configFilePath string) (c *AllConfig, err error) {
|
||||
func RewriteConfig(configFilePath string, allConfig *AllConfig) error {
|
||||
buf := bytes.Buffer{}
|
||||
enc := yaml.NewEncoder(&buf)
|
||||
defer enc.Close()
|
||||
defer func() {
|
||||
_ = enc.Close()
|
||||
}()
|
||||
enc.SetIndent(2)
|
||||
if err := enc.Encode(allConfig); err != nil {
|
||||
return err
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package constant
|
||||
|
||||
const (
|
||||
AIConfigProvider = "ai_config.provider"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultAIPromptConfigZhCN = `你是一个智能助手,可以帮助用户查询系统中的信息。用户问题:%s
|
||||
|
||||
你可以使用以下工具来查询系统信息:
|
||||
- get_questions: 搜索系统中已存在的问题,使用这个工具可以获取问题列表后注意需要使用 get_answers_by_question_id 获取问题的答案
|
||||
- get_answers_by_question_id: 根据问题ID获取该问题的所有答案
|
||||
- get_comments: 搜索评论信息
|
||||
- get_tags: 搜索标签信息
|
||||
- get_tag_detail: 获取特定标签的详细信息
|
||||
- get_user: 搜索用户信息
|
||||
- semantic_search: 通过语义相似度搜索问题和答案。当用户的问题与现有内容概念相关但可能不匹配确切关键词时使用此工具。当 get_questions 关键词搜索返回较差结果时,请使用 semantic_search。
|
||||
|
||||
请根据用户的问题智能地使用这些工具来提供准确的答案。如果需要查询系统信息,请先使用相应的工具获取数据。`
|
||||
DefaultAIPromptConfigEnUS = `You are an intelligent assistant that can help users query information in the system. User question: %s
|
||||
|
||||
You can use the following tools to query system information:
|
||||
- get_questions: Search for existing questions in the system. After using this tool to get the question list, you need to use get_answers_by_question_id to get the answers to the questions
|
||||
- get_answers_by_question_id: Get all answers for a question based on question ID
|
||||
- get_comments: Search for comment information
|
||||
- get_tags: Search for tag information
|
||||
- get_tag_detail: Get detailed information about a specific tag
|
||||
- get_user: Search for user information
|
||||
- semantic_search: Search questions and answers by semantic meaning. Use this when the user's question relates conceptually to existing content but may not match exact keywords. When get_questions keyword search returns poor results, use semantic_search instead.
|
||||
|
||||
Please intelligently use these tools based on the user's question to provide accurate answers. If you need to query system information, please use the appropriate tools to get the data first.`
|
||||
)
|
||||
@@ -42,6 +42,9 @@ const (
|
||||
ConfigCacheTime = 1 * time.Hour
|
||||
ConnectorUserExternalInfoCacheKey = "answer:connector:"
|
||||
ConnectorUserExternalInfoCacheTime = 10 * time.Minute
|
||||
ConnectorOAuthStateCacheKey = "answer:connector:oauth-state:"
|
||||
ConnectorOAuthStateCacheTime = 10 * time.Minute
|
||||
ConnectorOAuthBindStateCacheTime = 5 * time.Minute
|
||||
SiteMapQuestionCacheKeyPrefix = "answer:sitemap:question:%d"
|
||||
SiteMapQuestionCacheTime = time.Hour
|
||||
SitemapMaxSize = 50000
|
||||
|
||||
@@ -23,3 +23,10 @@ const (
|
||||
AcceptLanguageFlag = "Accept-Language"
|
||||
ShortIDFlag = "Short-ID-Enabled"
|
||||
)
|
||||
|
||||
type ContextKey string
|
||||
|
||||
const (
|
||||
AcceptLanguageContextKey ContextKey = ContextKey(AcceptLanguageFlag)
|
||||
ShortIDContextKey ContextKey = ContextKey(ShortIDFlag)
|
||||
)
|
||||
|
||||
@@ -43,6 +43,9 @@ const (
|
||||
ColorSchemeLight = "light"
|
||||
ColorSchemeDark = "dark"
|
||||
ColorSchemeSystem = "system"
|
||||
|
||||
ThemeLayoutFullWidth = "Full-width"
|
||||
ThemeLayoutFixedWidth = "Fixed-width"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -20,15 +20,30 @@
|
||||
package constant
|
||||
|
||||
const (
|
||||
// SiteTypeLegal\SiteTypeLegal\SiteTypeWrite The following items will no longer be used.
|
||||
SiteTypeLegal = "legal"
|
||||
SiteTypeInterface = "interface"
|
||||
SiteTypeWrite = "write"
|
||||
|
||||
SiteTypeGeneral = "general"
|
||||
SiteTypeInterface = "interface"
|
||||
SiteTypeBranding = "branding"
|
||||
SiteTypeWrite = "write"
|
||||
SiteTypeLegal = "legal"
|
||||
SiteTypeSeo = "seo"
|
||||
SiteTypeLogin = "login"
|
||||
SiteTypeCustomCssHTML = "css-html"
|
||||
SiteTypeTheme = "theme"
|
||||
SiteTypePrivileges = "privileges"
|
||||
SiteTypeUsers = "users"
|
||||
|
||||
SiteTypeAdvanced = "advanced"
|
||||
SiteTypeQuestions = "questions"
|
||||
SiteTypeTags = "tags"
|
||||
|
||||
SiteTypeUsersSettings = "users_settings"
|
||||
SiteTypeInterfaceSettings = "interface_settings"
|
||||
|
||||
SiteTypePolicies = "policies"
|
||||
SiteTypeSecurity = "security"
|
||||
SiteTypeAI = "ai"
|
||||
SiteTypeFeatureToggle = "feature-toggle"
|
||||
SiteTypeMCP = "mcp"
|
||||
)
|
||||
|
||||
@@ -47,7 +47,7 @@ type Data struct {
|
||||
func NewData(db *xorm.Engine, cache cache.Cache) (*Data, func(), error) {
|
||||
cleanup := func() {
|
||||
log.Info("closing the data resources")
|
||||
db.Close()
|
||||
_ = db.Close()
|
||||
}
|
||||
return &Data{DB: db, Cache: cache}, cleanup, nil
|
||||
}
|
||||
|
||||
@@ -21,18 +21,18 @@ package handler
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/apache/answer/internal/base/constant"
|
||||
"net/http"
|
||||
|
||||
"github.com/apache/answer/internal/base/reason"
|
||||
"github.com/apache/answer/internal/base/validator"
|
||||
"github.com/gin-gonic/gin"
|
||||
myErrors "github.com/segmentfault/pacman/errors"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// HandleResponse Handle response body
|
||||
func HandleResponse(ctx *gin.Context, err error, data interface{}) {
|
||||
lang := GetLang(ctx)
|
||||
func HandleResponse(ctx *gin.Context, err error, data any) {
|
||||
lang := GetLangByCtx(ctx)
|
||||
// no error
|
||||
if err == nil {
|
||||
ctx.JSON(http.StatusOK, NewRespBodyData(http.StatusOK, reason.Success, data).TrMsg(lang))
|
||||
@@ -61,9 +61,8 @@ func HandleResponse(ctx *gin.Context, err error, data interface{}) {
|
||||
}
|
||||
|
||||
// BindAndCheck bind request and check
|
||||
func BindAndCheck(ctx *gin.Context, data interface{}) bool {
|
||||
lang := GetLang(ctx)
|
||||
ctx.Set(constant.AcceptLanguageFlag, lang)
|
||||
func BindAndCheck(ctx *gin.Context, data any) bool {
|
||||
lang := GetLangByCtx(ctx)
|
||||
if err := ctx.ShouldBind(data); err != nil {
|
||||
log.Errorf("http_handle BindAndCheck fail, %s", err.Error())
|
||||
HandleResponse(ctx, myErrors.New(http.StatusBadRequest, reason.RequestFormatError), nil)
|
||||
@@ -79,8 +78,8 @@ func BindAndCheck(ctx *gin.Context, data interface{}) bool {
|
||||
}
|
||||
|
||||
// BindAndCheckReturnErr bind request and check
|
||||
func BindAndCheckReturnErr(ctx *gin.Context, data interface{}) (errFields []*validator.FormErrorField) {
|
||||
lang := GetLang(ctx)
|
||||
func BindAndCheckReturnErr(ctx *gin.Context, data any) (errFields []*validator.FormErrorField) {
|
||||
lang := GetLangByCtx(ctx)
|
||||
if err := ctx.ShouldBind(data); err != nil {
|
||||
log.Errorf("http_handle BindAndCheck fail, %s", err.Error())
|
||||
HandleResponse(ctx, myErrors.New(http.StatusBadRequest, reason.RequestFormatError), nil)
|
||||
|
||||
@@ -27,18 +27,19 @@ import (
|
||||
"github.com/segmentfault/pacman/i18n"
|
||||
)
|
||||
|
||||
// GetLang get language from header
|
||||
func GetLang(ctx *gin.Context) i18n.Language {
|
||||
acceptLanguage := ctx.GetHeader(constant.AcceptLanguageFlag)
|
||||
if len(acceptLanguage) == 0 {
|
||||
return i18n.DefaultLanguage
|
||||
}
|
||||
return i18n.Language(acceptLanguage)
|
||||
}
|
||||
|
||||
// GetLangByCtx get language from header
|
||||
func GetLangByCtx(ctx context.Context) i18n.Language {
|
||||
acceptLanguage, ok := ctx.Value(constant.AcceptLanguageFlag).(i18n.Language)
|
||||
if ginCtx, ok := ctx.(*gin.Context); ok {
|
||||
acceptLanguage, ok := ginCtx.Get(constant.AcceptLanguageFlag)
|
||||
if ok {
|
||||
if acceptLanguage, ok := acceptLanguage.(i18n.Language); ok {
|
||||
return acceptLanguage
|
||||
}
|
||||
return i18n.DefaultLanguage
|
||||
}
|
||||
}
|
||||
|
||||
acceptLanguage, ok := ctx.Value(constant.AcceptLanguageContextKey).(i18n.Language)
|
||||
if ok {
|
||||
return acceptLanguage
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ type RespBody struct {
|
||||
// response message
|
||||
Message string `json:"msg"`
|
||||
// response data
|
||||
Data interface{} `json:"data"`
|
||||
Data any `json:"data"`
|
||||
}
|
||||
|
||||
// TrMsg translate the reason cause as a message
|
||||
@@ -63,7 +63,7 @@ func NewRespBodyFromError(e *errors.Error) *RespBody {
|
||||
}
|
||||
|
||||
// NewRespBodyData new response body with data
|
||||
func NewRespBodyData(code int, reason string, data interface{}) *RespBody {
|
||||
func NewRespBodyData(code int, reason string, data any) *RespBody {
|
||||
return &RespBody{
|
||||
Code: code,
|
||||
Reason: reason,
|
||||
|
||||
@@ -23,11 +23,23 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/apache/answer/internal/base/constant"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// GetEnableShortID get language from header
|
||||
// GetEnableShortID get short id flag from context
|
||||
func GetEnableShortID(ctx context.Context) bool {
|
||||
flag, ok := ctx.Value(constant.ShortIDFlag).(bool)
|
||||
// Check gin context first (set by ShortIDMiddleware via ctx.Set)
|
||||
if ginCtx, ok := ctx.(*gin.Context); ok {
|
||||
flag, ok := ginCtx.Get(constant.ShortIDFlag)
|
||||
if ok {
|
||||
if flag, ok := flag.(bool); ok {
|
||||
return flag
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
// Fallback for non-gin contexts (e.g., SitemapCron uses context.WithValue)
|
||||
flag, ok := ctx.Value(constant.ShortIDContextKey).(bool)
|
||||
if ok {
|
||||
return flag
|
||||
}
|
||||
|
||||
@@ -20,20 +20,26 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/apache/answer/internal/base/constant"
|
||||
"github.com/apache/answer/internal/base/handler"
|
||||
"github.com/apache/answer/internal/base/translator"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/segmentfault/pacman/i18n"
|
||||
"golang.org/x/text/language"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const maxAcceptLanguageLength = 256
|
||||
|
||||
// ExtractAndSetAcceptLanguage extract accept language from header and set to context
|
||||
func ExtractAndSetAcceptLanguage(ctx *gin.Context) {
|
||||
// The language of our front-end configuration, like en_US
|
||||
lang := handler.GetLang(ctx)
|
||||
tag, _, err := language.ParseAcceptLanguage(string(lang))
|
||||
acceptLanguage := ctx.GetHeader(constant.AcceptLanguageFlag)
|
||||
if len(acceptLanguage) > maxAcceptLanguageLength {
|
||||
ctx.Set(constant.AcceptLanguageFlag, i18n.LanguageEnglish)
|
||||
return
|
||||
}
|
||||
tag, _, err := language.ParseAcceptLanguage(acceptLanguage)
|
||||
if err != nil || len(tag) == 0 {
|
||||
ctx.Set(constant.AcceptLanguageFlag, i18n.LanguageEnglish)
|
||||
return
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/apache/answer/internal/base/handler"
|
||||
"github.com/apache/answer/internal/base/reason"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/segmentfault/pacman/errors"
|
||||
)
|
||||
|
||||
// AuthAPIKey middleware to authenticate API key
|
||||
func (am *AuthUserMiddleware) AuthAPIKey() gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
token := ExtractToken(ctx)
|
||||
if len(token) == 0 {
|
||||
handler.HandleResponse(ctx, errors.Unauthorized(reason.UnauthorizedError), nil)
|
||||
ctx.Abort()
|
||||
return
|
||||
}
|
||||
pass, err := am.authService.AuthAPIKey(ctx, ctx.Request.Method == "GET", token)
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, errors.Unauthorized(reason.UnauthorizedError), nil)
|
||||
ctx.Abort()
|
||||
return
|
||||
}
|
||||
if !pass {
|
||||
handler.HandleResponse(ctx, errors.Unauthorized(reason.UnauthorizedError), nil)
|
||||
ctx.Abort()
|
||||
return
|
||||
}
|
||||
ctx.Next()
|
||||
}
|
||||
}
|
||||
@@ -80,7 +80,7 @@ func (am *AuthUserMiddleware) Auth() gin.HandlerFunc {
|
||||
func (am *AuthUserMiddleware) EjectUserBySiteInfo() gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
mustLogin := false
|
||||
siteInfo, _ := am.siteInfoCommonService.GetSiteLogin(ctx)
|
||||
siteInfo, _ := am.siteInfoCommonService.GetSiteSecurity(ctx)
|
||||
if siteInfo != nil {
|
||||
mustLogin = siteInfo.LoginRequired
|
||||
}
|
||||
@@ -116,6 +116,10 @@ func (am *AuthUserMiddleware) MustAuthWithoutAccountAvailable() gin.HandlerFunc
|
||||
ctx.Abort()
|
||||
return
|
||||
}
|
||||
// Check API key scope
|
||||
if am.AuthAPIKeyScope(ctx, token) {
|
||||
return
|
||||
}
|
||||
userInfo, err := am.authService.GetUserCacheInfo(ctx, token)
|
||||
if err != nil || userInfo == nil {
|
||||
handler.HandleResponse(ctx, errors.Unauthorized(reason.UnauthorizedError), nil)
|
||||
@@ -141,6 +145,10 @@ func (am *AuthUserMiddleware) MustAuthAndAccountAvailable() gin.HandlerFunc {
|
||||
ctx.Abort()
|
||||
return
|
||||
}
|
||||
// Check API key scope
|
||||
if am.AuthAPIKeyScope(ctx, token) {
|
||||
return
|
||||
}
|
||||
userInfo, err := am.authService.GetUserCacheInfo(ctx, token)
|
||||
if err != nil || userInfo == nil {
|
||||
handler.HandleResponse(ctx, errors.Unauthorized(reason.UnauthorizedError), nil)
|
||||
@@ -184,7 +192,22 @@ func (am *AuthUserMiddleware) AdminAuth() gin.HandlerFunc {
|
||||
return
|
||||
}
|
||||
if userInfo != nil {
|
||||
if userInfo.EmailStatus == entity.EmailStatusToBeVerified {
|
||||
_ = am.authService.RemoveAdminUserCacheInfo(ctx, token)
|
||||
handler.HandleResponse(ctx, errors.Forbidden(reason.EmailNeedToBeVerified),
|
||||
&schema.ForbiddenResp{Type: schema.ForbiddenReasonTypeInactive})
|
||||
ctx.Abort()
|
||||
return
|
||||
}
|
||||
if userInfo.UserStatus == entity.UserStatusSuspended {
|
||||
_ = am.authService.RemoveAdminUserCacheInfo(ctx, token)
|
||||
handler.HandleResponse(ctx, errors.Forbidden(reason.UserSuspended),
|
||||
&schema.ForbiddenResp{Type: schema.ForbiddenReasonTypeUserSuspended})
|
||||
ctx.Abort()
|
||||
return
|
||||
}
|
||||
if userInfo.UserStatus == entity.UserStatusDeleted {
|
||||
_ = am.authService.RemoveAdminUserCacheInfo(ctx, token)
|
||||
handler.HandleResponse(ctx, errors.Unauthorized(reason.UnauthorizedError), nil)
|
||||
ctx.Abort()
|
||||
return
|
||||
@@ -197,7 +220,7 @@ func (am *AuthUserMiddleware) AdminAuth() gin.HandlerFunc {
|
||||
|
||||
func (am *AuthUserMiddleware) CheckPrivateMode() gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
resp, err := am.siteInfoCommonService.GetSiteLogin(ctx)
|
||||
resp, err := am.siteInfoCommonService.GetSiteSecurity(ctx)
|
||||
if err != nil {
|
||||
ShowIndexPage(ctx)
|
||||
ctx.Abort()
|
||||
@@ -211,6 +234,26 @@ func (am *AuthUserMiddleware) CheckPrivateMode() gin.HandlerFunc {
|
||||
ctx.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func (am *AuthUserMiddleware) AuthAPIKeyScope(ctx *gin.Context, accessToken string) (apiHaveNoScope bool) {
|
||||
if !strings.HasPrefix(accessToken, "sk_") {
|
||||
return false
|
||||
}
|
||||
var err error
|
||||
pass, err := am.authService.AuthAPIKey(ctx, ctx.Request.Method == "GET", accessToken)
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, errors.Forbidden(reason.ForbiddenError), nil)
|
||||
ctx.Abort()
|
||||
return true
|
||||
}
|
||||
if !pass {
|
||||
handler.HandleResponse(ctx, errors.Forbidden(reason.ForbiddenError), nil)
|
||||
ctx.Abort()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func ShowIndexPage(ctx *gin.Context) {
|
||||
ctx.Header("content-type", "text/html;charset=utf-8")
|
||||
ctx.Header("X-Frame-Options", "DENY")
|
||||
|
||||
@@ -80,7 +80,6 @@ func (am *AvatarMiddleware) AvatarThumb() gin.HandlerFunc {
|
||||
}
|
||||
ctx.Abort()
|
||||
return
|
||||
|
||||
} else {
|
||||
urlInfo, err := url.Parse(uri)
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/apache/answer/internal/base/handler"
|
||||
"github.com/apache/answer/internal/base/reason"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/segmentfault/pacman/errors"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
)
|
||||
|
||||
// AuthMcpEnable check mcp is enabled
|
||||
func (am *AuthUserMiddleware) AuthMcpEnable() gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
mcpConfig, err := am.siteInfoCommonService.GetSiteMCP(ctx)
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, errors.InternalServer(reason.UnknownError), nil)
|
||||
ctx.Abort()
|
||||
return
|
||||
}
|
||||
if mcpConfig != nil && mcpConfig.Enabled {
|
||||
ctx.Next()
|
||||
return
|
||||
}
|
||||
handler.HandleResponse(ctx, errors.Forbidden(reason.ForbiddenError), nil)
|
||||
ctx.Abort()
|
||||
log.Error("abort mcp auth middleware, get mcp config error: ", err)
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ package middleware
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/apache/answer/internal/base/handler"
|
||||
"github.com/apache/answer/internal/base/reason"
|
||||
"github.com/apache/answer/internal/repo/limit"
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
|
||||
"github.com/apache/answer/internal/base/handler"
|
||||
"github.com/apache/answer/internal/base/reason"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
)
|
||||
|
||||
func Recovery(apiPrefixes ...string) gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
log.Errorf("panic recovered: %v\n%s", err, debug.Stack())
|
||||
|
||||
// Headers/body already flushed (SSE or any streamed response).
|
||||
// We can no longer rewrite the response cleanly; just stop the chain.
|
||||
if ctx.Writer.Written() {
|
||||
ctx.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
path := ctx.Request.URL.Path
|
||||
for _, p := range apiPrefixes {
|
||||
if strings.HasPrefix(path, p) {
|
||||
ctx.AbortWithStatusJSON(http.StatusInternalServerError,
|
||||
handler.NewRespBody(http.StatusInternalServerError, reason.UnknownError).
|
||||
TrMsg(handler.GetLangByCtx(ctx)),
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ctx.AbortWithStatus(http.StatusInternalServerError)
|
||||
}
|
||||
}()
|
||||
ctx.Next()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// Panic on an API path returns the project's unified JSON 500.
|
||||
func TestRecovery_APIPathPanic(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
r := gin.New()
|
||||
r.Use(Recovery("/api"))
|
||||
r.GET("/api/panic", func(ctx *gin.Context) {
|
||||
panic("test panic")
|
||||
})
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest(http.MethodGet, "/api/panic", nil)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusInternalServerError {
|
||||
t.Errorf("expected 500, got %d", w.Code)
|
||||
}
|
||||
|
||||
var body map[string]any
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &body); err != nil {
|
||||
t.Fatalf("response is not valid JSON: %v", err)
|
||||
}
|
||||
if body["reason"] != "base.unknown" {
|
||||
t.Errorf("unexpected reason: %v", body["reason"])
|
||||
}
|
||||
}
|
||||
|
||||
// Panic on a non-API path returns a bare 500 with no body, so the browser can
|
||||
// render its own error page instead of showing raw JSON.
|
||||
func TestRecovery_NonAPIPathPanic(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
r := gin.New()
|
||||
r.Use(Recovery("/api"))
|
||||
r.GET("/page", func(ctx *gin.Context) {
|
||||
panic("test panic")
|
||||
})
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest(http.MethodGet, "/page", nil)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusInternalServerError {
|
||||
t.Errorf("expected 500, got %d", w.Code)
|
||||
}
|
||||
if w.Body.Len() != 0 {
|
||||
t.Errorf("expected empty body for non-API path, got: %q", w.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
// Panic after the response has already started writing (SSE / streamed
|
||||
// responses). The middleware must not touch the response — status and body
|
||||
// already on the wire stay untouched, no JSON gets appended.
|
||||
func TestRecovery_PanicAfterResponseStarted(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
r := gin.New()
|
||||
r.Use(Recovery("/api"))
|
||||
r.GET("/api/stream", func(ctx *gin.Context) {
|
||||
ctx.Writer.WriteHeader(http.StatusOK)
|
||||
_, _ = ctx.Writer.Write([]byte("partial data"))
|
||||
panic("test panic after write")
|
||||
})
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest(http.MethodGet, "/api/stream", nil)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("expected status to remain 200 (already flushed), got %d", w.Code)
|
||||
}
|
||||
if w.Body.String() != "partial data" {
|
||||
t.Errorf("expected body to remain 'partial data' (no error JSON appended), got: %q", w.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
// Normal requests pass through unaffected.
|
||||
func TestRecovery_NoPanic(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
r := gin.New()
|
||||
r.Use(Recovery("/api"))
|
||||
r.GET("/api/ok", func(ctx *gin.Context) {
|
||||
ctx.String(http.StatusOK, "ok")
|
||||
})
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest(http.MethodGet, "/api/ok", nil)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("expected 200, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
@@ -41,11 +41,11 @@ func (am *AuthUserMiddleware) VisitAuth() gin.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
siteLogin, err := am.siteInfoCommonService.GetSiteLogin(ctx)
|
||||
siteSecurity, err := am.siteInfoCommonService.GetSiteSecurity(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !siteLogin.LoginRequired {
|
||||
if !siteSecurity.LoginRequired {
|
||||
ctx.Next()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ import (
|
||||
)
|
||||
|
||||
// Help xorm page helper
|
||||
func Help(page, pageSize int, rowsSlicePtr interface{}, rowElement interface{}, session *xorm.Session) (total int64, err error) {
|
||||
func Help(page, pageSize int, rowsSlicePtr any, rowElement any, session *xorm.Session) (total int64, err error) {
|
||||
page, pageSize = ValPageAndPageSize(page, pageSize)
|
||||
|
||||
sliceValue := reflect.Indirect(reflect.ValueOf(rowsSlicePtr))
|
||||
|
||||
@@ -25,8 +25,8 @@ import (
|
||||
|
||||
// PageModel page model
|
||||
type PageModel struct {
|
||||
Count int64 `json:"count"`
|
||||
List interface{} `json:"list"`
|
||||
Count int64 `json:"count"`
|
||||
List any `json:"list"`
|
||||
}
|
||||
|
||||
// PageCond page condition
|
||||
@@ -36,7 +36,7 @@ type PageCond struct {
|
||||
}
|
||||
|
||||
// NewPageModel new page model
|
||||
func NewPageModel(totalRecords int64, records interface{}) *PageModel {
|
||||
func NewPageModel(totalRecords int64, records any) *PageModel {
|
||||
sliceValue := reflect.Indirect(reflect.ValueOf(records))
|
||||
if sliceValue.Kind() != reflect.Slice {
|
||||
panic("not a slice")
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package queue
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/segmentfault/pacman/log"
|
||||
)
|
||||
|
||||
type Service[T any] interface {
|
||||
// Send enqueues a message to be processed asynchronously.
|
||||
Send(ctx context.Context, msg T)
|
||||
|
||||
// RegisterHandler sets the handler function for processing messages.
|
||||
RegisterHandler(handler func(ctx context.Context, msg T) error)
|
||||
|
||||
// Close gracefully shuts down the queue, waiting for pending messages to be processed.
|
||||
Close()
|
||||
}
|
||||
|
||||
// Queue is a generic message queue service that processes messages asynchronously.
|
||||
// It is thread-safe and supports graceful shutdown.
|
||||
type Queue[T any] struct {
|
||||
name string
|
||||
queue chan T
|
||||
handler func(ctx context.Context, msg T) error
|
||||
mu sync.RWMutex
|
||||
closed bool
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
// New creates a new queue with the given name and buffer size.
|
||||
func New[T any](name string, bufferSize int) *Queue[T] {
|
||||
q := &Queue[T]{
|
||||
name: name,
|
||||
queue: make(chan T, bufferSize),
|
||||
}
|
||||
q.startWorker()
|
||||
return q
|
||||
}
|
||||
|
||||
// Send enqueues a message to be processed asynchronously.
|
||||
// It will block if the queue is full.
|
||||
func (q *Queue[T]) Send(ctx context.Context, msg T) {
|
||||
q.mu.RLock()
|
||||
defer q.mu.RUnlock()
|
||||
|
||||
if q.closed {
|
||||
log.Warnf("[%s] queue is closed, dropping message", q.name)
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case q.queue <- msg:
|
||||
log.Debugf("[%s] enqueued message: %+v", q.name, msg)
|
||||
case <-ctx.Done():
|
||||
log.Warnf("[%s] context cancelled while sending message", q.name)
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterHandler sets the handler function for processing messages.
|
||||
// This is thread-safe and can be called at any time.
|
||||
func (q *Queue[T]) RegisterHandler(handler func(ctx context.Context, msg T) error) {
|
||||
q.mu.Lock()
|
||||
defer q.mu.Unlock()
|
||||
q.handler = handler
|
||||
}
|
||||
|
||||
// Close gracefully shuts down the queue, waiting for pending messages to be processed.
|
||||
func (q *Queue[T]) Close() {
|
||||
q.mu.Lock()
|
||||
if q.closed {
|
||||
q.mu.Unlock()
|
||||
return
|
||||
}
|
||||
q.closed = true
|
||||
q.mu.Unlock()
|
||||
|
||||
close(q.queue)
|
||||
q.wg.Wait()
|
||||
log.Infof("[%s] queue closed", q.name)
|
||||
}
|
||||
|
||||
// startWorker starts the background goroutine that processes messages.
|
||||
func (q *Queue[T]) startWorker() {
|
||||
q.wg.Go(func() {
|
||||
for msg := range q.queue {
|
||||
q.processMessage(msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// processMessage handles a single message with proper synchronization.
|
||||
func (q *Queue[T]) processMessage(msg T) {
|
||||
q.mu.RLock()
|
||||
handler := q.handler
|
||||
q.mu.RUnlock()
|
||||
|
||||
if handler == nil {
|
||||
log.Warnf("[%s] no handler registered, dropping message: %+v", q.name, msg)
|
||||
return
|
||||
}
|
||||
|
||||
// Use background context for async processing
|
||||
// TODO: Consider adding timeout or using a derived context
|
||||
if err := handler(context.TODO(), msg); err != nil {
|
||||
log.Errorf("[%s] handler error: %v", q.name, err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package queue
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type testMessage struct {
|
||||
ID int
|
||||
Data string
|
||||
}
|
||||
|
||||
func TestQueue_SendAndReceive(t *testing.T) {
|
||||
q := New[*testMessage]("test", 10)
|
||||
defer q.Close()
|
||||
|
||||
received := make(chan *testMessage, 1)
|
||||
q.RegisterHandler(func(ctx context.Context, msg *testMessage) error {
|
||||
received <- msg
|
||||
return nil
|
||||
})
|
||||
|
||||
msg := &testMessage{ID: 1, Data: "hello"}
|
||||
q.Send(context.Background(), msg)
|
||||
|
||||
select {
|
||||
case r := <-received:
|
||||
if r.ID != msg.ID || r.Data != msg.Data {
|
||||
t.Errorf("received message mismatch: got %+v, want %+v", r, msg)
|
||||
}
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("timeout waiting for message")
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueue_MultipleMessages(t *testing.T) {
|
||||
q := New[*testMessage]("test", 10)
|
||||
defer q.Close()
|
||||
|
||||
var count atomic.Int32
|
||||
var wg sync.WaitGroup
|
||||
numMessages := 100
|
||||
wg.Add(numMessages)
|
||||
|
||||
q.RegisterHandler(func(ctx context.Context, msg *testMessage) error {
|
||||
count.Add(1)
|
||||
wg.Done()
|
||||
return nil
|
||||
})
|
||||
|
||||
for i := range numMessages {
|
||||
q.Send(context.Background(), &testMessage{ID: i})
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(done)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
if int(count.Load()) != numMessages {
|
||||
t.Errorf("expected %d messages, got %d", numMessages, count.Load())
|
||||
}
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatalf("timeout: only received %d of %d messages", count.Load(), numMessages)
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueue_NoHandlerDropsMessage(t *testing.T) {
|
||||
q := New[*testMessage]("test", 10)
|
||||
defer q.Close()
|
||||
|
||||
// Send without handler - should not panic
|
||||
q.Send(context.Background(), &testMessage{ID: 1})
|
||||
|
||||
// Give time for the message to be processed (dropped)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
func TestQueue_RegisterHandlerAfterSend(t *testing.T) {
|
||||
q := New[*testMessage]("test", 10)
|
||||
defer q.Close()
|
||||
|
||||
received := make(chan *testMessage, 1)
|
||||
|
||||
// Send first
|
||||
q.Send(context.Background(), &testMessage{ID: 1})
|
||||
|
||||
// Small delay then register handler
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
q.RegisterHandler(func(ctx context.Context, msg *testMessage) error {
|
||||
received <- msg
|
||||
return nil
|
||||
})
|
||||
|
||||
// Send another message that should be received
|
||||
q.Send(context.Background(), &testMessage{ID: 2})
|
||||
|
||||
select {
|
||||
case r := <-received:
|
||||
if r.ID != 2 {
|
||||
// First message was dropped (no handler), second should be received
|
||||
t.Logf("received message ID: %d", r.ID)
|
||||
}
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("timeout waiting for message")
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueue_Close(t *testing.T) {
|
||||
q := New[*testMessage]("test", 10)
|
||||
|
||||
var count atomic.Int32
|
||||
q.RegisterHandler(func(ctx context.Context, msg *testMessage) error {
|
||||
count.Add(1)
|
||||
return nil
|
||||
})
|
||||
|
||||
// Send some messages
|
||||
for i := range 5 {
|
||||
q.Send(context.Background(), &testMessage{ID: i})
|
||||
}
|
||||
|
||||
// Close and wait
|
||||
q.Close()
|
||||
|
||||
// All messages should have been processed
|
||||
if count.Load() != 5 {
|
||||
t.Errorf("expected 5 messages processed, got %d", count.Load())
|
||||
}
|
||||
|
||||
// Sending after close should not panic
|
||||
q.Send(context.Background(), &testMessage{ID: 99})
|
||||
}
|
||||
|
||||
func TestQueue_ConcurrentSend(t *testing.T) {
|
||||
q := New[*testMessage]("test", 100)
|
||||
defer q.Close()
|
||||
|
||||
var count atomic.Int32
|
||||
q.RegisterHandler(func(ctx context.Context, msg *testMessage) error {
|
||||
count.Add(1)
|
||||
return nil
|
||||
})
|
||||
|
||||
var wg sync.WaitGroup
|
||||
numGoroutines := 10
|
||||
messagesPerGoroutine := 100
|
||||
|
||||
for i := range numGoroutines {
|
||||
wg.Add(1)
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
for j := range messagesPerGoroutine {
|
||||
q.Send(context.Background(), &testMessage{ID: id*1000 + j})
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
// Wait for processing
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
expected := int32(numGoroutines * messagesPerGoroutine)
|
||||
if count.Load() != expected {
|
||||
t.Errorf("expected %d messages, got %d", expected, count.Load())
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueue_ConcurrentRegisterHandler(t *testing.T) {
|
||||
q := New[*testMessage]("test", 10)
|
||||
defer q.Close()
|
||||
|
||||
// Concurrently register handlers - should not race
|
||||
var wg sync.WaitGroup
|
||||
for range 10 {
|
||||
wg.Go(func() {
|
||||
q.RegisterHandler(func(ctx context.Context, msg *testMessage) error {
|
||||
return nil
|
||||
})
|
||||
})
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// TestQueue_SendCloseRace is a regression test for the race condition between
|
||||
// Send and Close. Without proper synchronization, concurrent Send and Close
|
||||
// calls could cause a "send on closed channel" panic.
|
||||
// Run with: go test -race -run TestQueue_SendCloseRace
|
||||
func TestQueue_SendCloseRace(t *testing.T) {
|
||||
for i := range 100 {
|
||||
t.Run(fmt.Sprintf("iteration_%d", i), func(t *testing.T) {
|
||||
// Use large buffer to avoid blocking on channel send while holding RLock
|
||||
q := New[*testMessage]("test-race", 1000)
|
||||
q.RegisterHandler(func(ctx context.Context, msg *testMessage) error {
|
||||
return nil
|
||||
})
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
// Use cancellable context so senders can exit when Close is called
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
// Start multiple senders
|
||||
for j := range 10 {
|
||||
wg.Add(1)
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
for k := range 100 {
|
||||
q.Send(ctx, &testMessage{ID: id*1000 + k})
|
||||
}
|
||||
}(j)
|
||||
}
|
||||
|
||||
// Close while senders are still running
|
||||
go func() {
|
||||
time.Sleep(time.Microsecond * 10)
|
||||
cancel() // Cancel context to unblock any waiting senders
|
||||
q.Close()
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@ const (
|
||||
QuestionAlreadyDeleted = "error.question.already_deleted"
|
||||
QuestionUnderReview = "error.question.under_review"
|
||||
QuestionContentCannotEmpty = "error.question.content_cannot_empty"
|
||||
QuestionContentLessThanMinimum = "error.question.content_less_than_minumum"
|
||||
QuestionContentLessThanMinimum = "error.question.content_less_than_minimum"
|
||||
AnswerNotFound = "error.answer.not_found"
|
||||
AnswerCannotDeleted = "error.answer.cannot_deleted"
|
||||
AnswerCannotUpdate = "error.answer.cannot_update"
|
||||
@@ -117,6 +117,7 @@ const (
|
||||
UserStatusSuspendedForever = "error.user.status_suspended_forever"
|
||||
UserStatusSuspendedUntil = "error.user.status_suspended_until"
|
||||
UserStatusDeleted = "error.user.status_deleted"
|
||||
ErrFeatureDisabled = "error.feature.disabled"
|
||||
)
|
||||
|
||||
// user external login reasons
|
||||
|
||||
@@ -22,6 +22,8 @@ package server
|
||||
import (
|
||||
"html/template"
|
||||
"io/fs"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
brotli "github.com/anargu/gin-brotli"
|
||||
"github.com/apache/answer/internal/base/middleware"
|
||||
@@ -44,19 +46,32 @@ func NewHTTPServer(debug bool,
|
||||
pluginAPIRouter *router.PluginAPIRouter,
|
||||
uiConf *UI,
|
||||
) *gin.Engine {
|
||||
|
||||
if debug {
|
||||
gin.SetMode(gin.DebugMode)
|
||||
} else {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
}
|
||||
r := gin.New()
|
||||
r.Use(brotli.Brotli(brotli.DefaultCompression), middleware.ExtractAndSetAcceptLanguage, shortIDMiddleware.SetShortIDFlag())
|
||||
r.Use(middleware.Recovery(
|
||||
uiConf.APIBaseURL+"/answer/api/v1",
|
||||
uiConf.APIBaseURL+"/answer/admin/api",
|
||||
))
|
||||
r.Use(func(ctx *gin.Context) {
|
||||
if strings.Contains(ctx.Request.URL.Path, "/chat/completions") {
|
||||
return
|
||||
}
|
||||
brotli.Brotli(brotli.DefaultCompression)(ctx)
|
||||
}, middleware.ExtractAndSetAcceptLanguage, shortIDMiddleware.SetShortIDFlag())
|
||||
r.GET("/healthz", func(ctx *gin.Context) { ctx.String(200, "OK") })
|
||||
|
||||
html, _ := fs.Sub(ui.Template, "template")
|
||||
htmlTemplate := template.Must(template.New("").Funcs(funcMap).ParseFS(html, "*"))
|
||||
r.SetHTMLTemplate(htmlTemplate)
|
||||
templatePath := os.Getenv("ANSWER_TEMPLATE_PATH")
|
||||
if templatePath != "" {
|
||||
r.LoadHTMLGlob(templatePath)
|
||||
} else {
|
||||
html, _ := fs.Sub(ui.Template, "template")
|
||||
htmlTemplate := template.Must(template.New("").Funcs(funcMap).ParseFS(html, "*"))
|
||||
r.SetHTMLTemplate(htmlTemplate)
|
||||
}
|
||||
r.Use(middleware.HeadersByRequestURI())
|
||||
viewRouter.Register(r, uiConf.BaseURL)
|
||||
|
||||
@@ -102,5 +117,10 @@ func NewHTTPServer(debug bool,
|
||||
agent.RegisterAuthAdminRouter(adminauthV1)
|
||||
return nil
|
||||
})
|
||||
|
||||
// mcp
|
||||
mcpAPIGroup := r.Group(uiConf.APIBaseURL + "/answer/api/v1")
|
||||
mcpAPIGroup.Use(authUserMiddleware.AuthMcpEnable(), authUserMiddleware.AuthAPIKey())
|
||||
answerRouter.RegisterMCPRouter(mcpAPIGroup)
|
||||
return r
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ package server
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"math"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -64,7 +63,7 @@ var funcMap = template.FuncMap{
|
||||
"formatLinkNofollow": func(data string) template.HTML {
|
||||
return template.HTML(FormatLinkNofollow(data))
|
||||
},
|
||||
"translator": func(la i18n.Language, data string, params ...interface{}) string {
|
||||
"translator": func(la i18n.Language, data string, params ...any) string {
|
||||
trans := translator.GlobalTrans.Tr(la, data)
|
||||
|
||||
if len(params) > 0 && len(params)%2 == 0 {
|
||||
@@ -107,15 +106,15 @@ var funcMap = template.FuncMap{
|
||||
}
|
||||
|
||||
if between >= 60 && between < 3600 {
|
||||
min := math.Floor(float64(between / 60))
|
||||
min := between / 60
|
||||
trans = translator.GlobalTrans.Tr(la, "ui.dates.x_minutes_ago")
|
||||
return strings.ReplaceAll(trans, "{{count}}", strconv.FormatFloat(min, 'f', 0, 64))
|
||||
return strings.ReplaceAll(trans, "{{count}}", strconv.FormatInt(min, 10))
|
||||
}
|
||||
|
||||
if between >= 3600 && between < 3600*24 {
|
||||
h := math.Floor(float64(between / 3600))
|
||||
h := between / 3600
|
||||
trans = translator.GlobalTrans.Tr(la, "ui.dates.x_hours_ago")
|
||||
return strings.ReplaceAll(trans, "{{count}}", strconv.FormatFloat(h, 'f', 0, 64))
|
||||
return strings.ReplaceAll(trans, "{{count}}", strconv.FormatInt(h, 10))
|
||||
}
|
||||
|
||||
if between >= 3600*24 &&
|
||||
@@ -128,16 +127,14 @@ var funcMap = template.FuncMap{
|
||||
trans = translator.GlobalTrans.Tr(la, "ui.dates.long_date_with_year")
|
||||
return day.Format(timestamp, trans, tz)
|
||||
},
|
||||
"wrapComments": func(comments []*schema.GetCommentResp, la i18n.Language, tz string) map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"wrapComments": func(comments []*schema.GetCommentResp, la i18n.Language, tz string) map[string]any {
|
||||
return map[string]any{
|
||||
"comments": comments,
|
||||
"language": la,
|
||||
"timezone": tz,
|
||||
}
|
||||
},
|
||||
"urlTitle": func(title string) string {
|
||||
return htmltext.UrlTitle(title)
|
||||
},
|
||||
"urlTitle": htmltext.UrlTitle,
|
||||
}
|
||||
|
||||
func FormatLinkNofollow(html string) string {
|
||||
|
||||
@@ -23,6 +23,8 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/google/wire"
|
||||
myTran "github.com/segmentfault/pacman/contrib/i18n"
|
||||
@@ -76,14 +78,14 @@ func NewTranslator(c *I18n) (tr i18n.Translator, err error) {
|
||||
|
||||
// parse the backend translation
|
||||
originalTr := struct {
|
||||
Backend map[string]map[string]interface{} `yaml:"backend"`
|
||||
UI map[string]interface{} `yaml:"ui"`
|
||||
Plugin map[string]interface{} `yaml:"plugin"`
|
||||
Backend map[string]map[string]any `yaml:"backend"`
|
||||
UI map[string]any `yaml:"ui"`
|
||||
Plugin map[string]any `yaml:"plugin"`
|
||||
}{}
|
||||
if err = yaml.Unmarshal(buf, &originalTr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
translation := make(map[string]interface{}, 0)
|
||||
translation := make(map[string]any, 0)
|
||||
for k, v := range originalTr.Backend {
|
||||
translation[k] = v
|
||||
}
|
||||
@@ -100,6 +102,7 @@ func NewTranslator(c *I18n) (tr i18n.Translator, err error) {
|
||||
// add translator use backend translation
|
||||
if err = myTran.AddTranslator(content, file.Name()); err != nil {
|
||||
log.Debugf("add translator failed: %s %s", file.Name(), err)
|
||||
reportTranslatorFormatError(file.Name(), buf)
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -160,3 +163,163 @@ func TrWithData(lang i18n.Language, key string, templateData any) string {
|
||||
}
|
||||
return translation
|
||||
}
|
||||
|
||||
// reportTranslatorFormatError re-parses the YAML file to locate the invalid entry
|
||||
// when go-i18n fails to add the translator.
|
||||
func reportTranslatorFormatError(fileName string, content []byte) {
|
||||
var raw any
|
||||
if err := yaml.Unmarshal(content, &raw); err != nil {
|
||||
log.Errorf("parse translator file %s failed when diagnosing format error: %s", fileName, err)
|
||||
return
|
||||
}
|
||||
if err := inspectTranslatorNode(raw, nil, true); err != nil {
|
||||
log.Errorf("translator file %s invalid: %s", fileName, err)
|
||||
}
|
||||
}
|
||||
|
||||
func inspectTranslatorNode(node any, path []string, isRoot bool) error {
|
||||
switch data := node.(type) {
|
||||
case nil:
|
||||
if isRoot {
|
||||
return fmt.Errorf("root value is empty")
|
||||
}
|
||||
return fmt.Errorf("%s contains an empty value", formatTranslationPath(path))
|
||||
case string:
|
||||
if isRoot {
|
||||
return fmt.Errorf("root value must be an object but found string")
|
||||
}
|
||||
return nil
|
||||
case bool, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64:
|
||||
if isRoot {
|
||||
return fmt.Errorf("root value must be an object but found %T", data)
|
||||
}
|
||||
return fmt.Errorf("%s expects a string translation but found %T", formatTranslationPath(path), data)
|
||||
case map[string]any:
|
||||
if isMessageMap(data) {
|
||||
return nil
|
||||
}
|
||||
keys := make([]string, 0, len(data))
|
||||
for key := range data {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, key := range keys {
|
||||
if err := inspectTranslatorNode(data[key], append(path, key), false); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
case map[string]string:
|
||||
mapped := make(map[string]any, len(data))
|
||||
for k, v := range data {
|
||||
mapped[k] = v
|
||||
}
|
||||
return inspectTranslatorNode(mapped, path, isRoot)
|
||||
case map[any]any:
|
||||
if isMessageMap(data) {
|
||||
return nil
|
||||
}
|
||||
type kv struct {
|
||||
key string
|
||||
val any
|
||||
}
|
||||
items := make([]kv, 0, len(data))
|
||||
for key, val := range data {
|
||||
strKey, ok := key.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("%s uses a non-string key %#v", formatTranslationPath(path), key)
|
||||
}
|
||||
items = append(items, kv{key: strKey, val: val})
|
||||
}
|
||||
sort.Slice(items, func(i, j int) bool {
|
||||
return items[i].key < items[j].key
|
||||
})
|
||||
for _, item := range items {
|
||||
if err := inspectTranslatorNode(item.val, append(path, item.key), false); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
case []any:
|
||||
for idx, child := range data {
|
||||
if err := inspectTranslatorNode(child, append(path, fmt.Sprintf("[%d]", idx)), false); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
case []map[string]any:
|
||||
for idx, child := range data {
|
||||
if err := inspectTranslatorNode(child, append(path, fmt.Sprintf("[%d]", idx)), false); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
if isRoot {
|
||||
return fmt.Errorf("root value must be an object but found %T", data)
|
||||
}
|
||||
return fmt.Errorf("%s contains unsupported value type %T", formatTranslationPath(path), data)
|
||||
}
|
||||
}
|
||||
|
||||
var translatorReservedKeys = []string{
|
||||
"id", "description", "hash", "leftdelim", "rightdelim",
|
||||
"zero", "one", "two", "few", "many", "other",
|
||||
}
|
||||
|
||||
func isMessageMap(data any) bool {
|
||||
switch v := data.(type) {
|
||||
case map[string]any:
|
||||
for _, key := range translatorReservedKeys {
|
||||
val, ok := v[key]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if _, ok := val.(string); ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
case map[string]string:
|
||||
for _, key := range translatorReservedKeys {
|
||||
val, ok := v[key]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if val != "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
case map[any]any:
|
||||
for _, key := range translatorReservedKeys {
|
||||
val, ok := v[key]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if _, ok := val.(string); ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func formatTranslationPath(path []string) string {
|
||||
if len(path) == 0 {
|
||||
return "root"
|
||||
}
|
||||
var b strings.Builder
|
||||
for _, part := range path {
|
||||
if part == "" {
|
||||
continue
|
||||
}
|
||||
if part[0] == '[' {
|
||||
b.WriteString(part)
|
||||
continue
|
||||
}
|
||||
if b.Len() > 0 {
|
||||
b.WriteByte('.')
|
||||
}
|
||||
b.WriteString(part)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ func Sanitizer(fl validator.FieldLevel) (res bool) {
|
||||
switch field.Kind() {
|
||||
case reflect.String:
|
||||
filter := bluemonday.UGCPolicy()
|
||||
content := strings.Replace(filter.Sanitize(field.String()), "&", "&", -1)
|
||||
content := strings.ReplaceAll(filter.Sanitize(field.String()), "&", "&")
|
||||
field.SetString(content)
|
||||
return true
|
||||
case reflect.Chan, reflect.Map, reflect.Slice, reflect.Array:
|
||||
@@ -187,7 +187,7 @@ func GetValidatorByLang(lang i18n.Language) *MyValidator {
|
||||
}
|
||||
|
||||
// Check /
|
||||
func (m *MyValidator) Check(value interface{}) (errFields []*FormErrorField, err error) {
|
||||
func (m *MyValidator) Check(value any) (errFields []*FormErrorField, err error) {
|
||||
defer func() {
|
||||
if len(errFields) == 0 {
|
||||
return
|
||||
@@ -261,7 +261,7 @@ type Checker interface {
|
||||
Check() (errField []*FormErrorField, err error)
|
||||
}
|
||||
|
||||
func getObjectTagByFieldName(obj interface{}, fieldName string) (tag string) {
|
||||
func getObjectTagByFieldName(obj any, fieldName string) (tag string) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
log.Error(err)
|
||||
|
||||
+90
-56
@@ -34,7 +34,6 @@ import (
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/apache/answer/pkg/dir"
|
||||
"github.com/apache/answer/pkg/writer"
|
||||
"github.com/apache/answer/ui"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
@@ -141,6 +140,12 @@ func formatPlugins(plugins []string) (formatted []*pluginInfo) {
|
||||
info := &pluginInfo{}
|
||||
plugin, info.Path, _ = strings.Cut(plugin, "=")
|
||||
info.Name, info.Version, _ = strings.Cut(plugin, "@")
|
||||
// Resolve local path to absolute since build runs in a temp directory
|
||||
if len(info.Path) > 0 {
|
||||
if absPath, err := filepath.Abs(info.Path); err == nil {
|
||||
info.Path = absPath
|
||||
}
|
||||
}
|
||||
formatted = append(formatted, info)
|
||||
}
|
||||
return formatted
|
||||
@@ -186,7 +191,12 @@ func createMainGoFile(b *buildingMaterial) (err error) {
|
||||
for _, p := range b.plugins {
|
||||
// If user set a path, use it to replace the module with local path
|
||||
if len(p.Path) > 0 {
|
||||
replacement := fmt.Sprintf("%s@%s=%s", p.Name, p.Version, p.Path)
|
||||
var replacement string
|
||||
if len(p.Version) > 0 {
|
||||
replacement = fmt.Sprintf("%s@%s=%s", p.Name, p.Version, p.Path)
|
||||
} else {
|
||||
replacement = fmt.Sprintf("%s=%s", p.Name, p.Path)
|
||||
}
|
||||
err = b.newExecCmd("go", "mod", "edit", "-replace", replacement).Run()
|
||||
} else if len(p.Version) > 0 {
|
||||
// If user specify a version, use it to get specific version of the module
|
||||
@@ -201,9 +211,27 @@ func createMainGoFile(b *buildingMaterial) (err error) {
|
||||
|
||||
// downloadGoModFile run go mod commands to download dependencies
|
||||
func downloadGoModFile(b *buildingMaterial) (err error) {
|
||||
// If user specify a module replacement, use it. Otherwise, use the latest version.
|
||||
if len(b.answerModuleReplacement) > 0 {
|
||||
replacement := fmt.Sprintf("%s=%s", "github.com/apache/answer", b.answerModuleReplacement)
|
||||
answerReplacement := b.answerModuleReplacement
|
||||
|
||||
// If no replacement specified and current binary is v2+, auto-determine replacement.
|
||||
// This is needed because go mod tidy would otherwise resolve github.com/apache/answer
|
||||
// to the latest v1.x version, causing v2+ features (e.g. AI/MCP) to disappear.
|
||||
if len(answerReplacement) == 0 && b.originalAnswerInfo.Version != "" {
|
||||
ver, verErr := semver.NewVersion(strings.TrimPrefix(b.originalAnswerInfo.Version, "v"))
|
||||
if verErr == nil && ver.Major() >= 2 {
|
||||
answerReplacement = fmt.Sprintf("github.com/apache/answer@%s", b.originalAnswerInfo.Version)
|
||||
}
|
||||
}
|
||||
|
||||
if len(answerReplacement) > 0 {
|
||||
// For v2+ versioned module paths (e.g. github.com/apache/answer@v2.0.0),
|
||||
// go mod tidy rejects the version because the module path lacks a /v2 suffix.
|
||||
// Work around this by cloning the repo locally and using a local path replacement.
|
||||
localPath, resolveErr := resolveAnswerModuleReplacement(answerReplacement, b.tmpDir)
|
||||
if resolveErr != nil {
|
||||
return resolveErr
|
||||
}
|
||||
replacement := fmt.Sprintf("%s=%s", "github.com/apache/answer", localPath)
|
||||
err = b.newExecCmd("go", "mod", "edit", "-replace", replacement).Run()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -222,6 +250,56 @@ func downloadGoModFile(b *buildingMaterial) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// resolveAnswerModuleReplacement resolves the ANSWER_MODULE value to a usable local path or
|
||||
// remote replacement string. For v2+ versioned module paths (e.g. github.com/apache/answer@v2.0.0),
|
||||
// Go module system rejects the version because the module path has no /v2 suffix. In that case
|
||||
// the repository is cloned locally and the local path is returned instead.
|
||||
func resolveAnswerModuleReplacement(replacement, tmpDir string) (string, error) {
|
||||
// Local paths can be used as-is.
|
||||
if strings.HasPrefix(replacement, "/") || strings.HasPrefix(replacement, "./") || strings.HasPrefix(replacement, "../") {
|
||||
return replacement, nil
|
||||
}
|
||||
|
||||
// Parse module@version format.
|
||||
moduleName, version, hasVersion := strings.Cut(replacement, "@")
|
||||
if !hasVersion {
|
||||
return replacement, nil
|
||||
}
|
||||
|
||||
// Only handle v2+ versions on module paths without the /vN suffix.
|
||||
ver, err := semver.StrictNewVersion(strings.TrimPrefix(version, "v"))
|
||||
if err != nil || ver.Major() < 2 {
|
||||
return replacement, nil
|
||||
}
|
||||
if strings.HasSuffix(moduleName, fmt.Sprintf("/v%d", ver.Major())) {
|
||||
return replacement, nil
|
||||
}
|
||||
|
||||
// Clone the repo to a local directory and return its path.
|
||||
gitURL := "https://" + moduleName
|
||||
tag := "v" + strings.TrimPrefix(version, "v")
|
||||
localPath := filepath.Join(filepath.Dir(tmpDir), fmt.Sprintf("answer_src_%s", strings.ReplaceAll(version, ".", "_")))
|
||||
|
||||
if _, statErr := os.Stat(localPath); statErr == nil {
|
||||
fmt.Printf("[build] using cached local clone at %s\n", localPath)
|
||||
return localPath, nil
|
||||
}
|
||||
|
||||
fmt.Printf("[build] v2+ module detected, cloning %s@%s to local path %s...\n", moduleName, version, localPath)
|
||||
cloneCmd := exec.Command("git", "clone", "--depth=1", "--branch="+tag, gitURL, localPath)
|
||||
cloneCmd.Stdout = os.Stdout
|
||||
cloneCmd.Stderr = os.Stderr
|
||||
if err = cloneCmd.Run(); err != nil {
|
||||
return "", fmt.Errorf(
|
||||
"failed to clone %s@%s: %w\nTip: set ANSWER_MODULE to a local checkout path instead, e.g. ANSWER_MODULE=/path/to/answer",
|
||||
moduleName, version, err,
|
||||
)
|
||||
}
|
||||
|
||||
fmt.Printf("[build] successfully cloned to %s\n", localPath)
|
||||
return localPath, nil
|
||||
}
|
||||
|
||||
// movePluginToVendor move plugin to vendor dir
|
||||
// Traverse the plugins, and if the plugin path is not github.com/apache/answer-plugins, move the contents of the current plugin to the vendor/github.com/apache/answer-plugins/ directory.
|
||||
func movePluginToVendor(b *buildingMaterial) (err error) {
|
||||
@@ -300,50 +378,6 @@ func copyUIFiles(b *buildingMaterial) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// overwriteIndexTs overwrites index.ts file in ui/src/plugins/ dir
|
||||
func overwriteIndexTs(b *buildingMaterial) (err error) {
|
||||
localUIPluginDir := filepath.Join(b.tmpDir, "vendor/github.com/apache/answer/ui/src/plugins/")
|
||||
|
||||
folders, err := getFolders(localUIPluginDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get folders: %w", err)
|
||||
}
|
||||
|
||||
content := generateIndexTsContent(folders)
|
||||
err = os.WriteFile(filepath.Join(localUIPluginDir, "index.ts"), []byte(content), 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write index.ts: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getFolders(dir string) ([]string, error) {
|
||||
var folders []string
|
||||
files, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, file := range files {
|
||||
if file.IsDir() && file.Name() != "builtin" {
|
||||
folders = append(folders, file.Name())
|
||||
}
|
||||
}
|
||||
return folders, nil
|
||||
}
|
||||
|
||||
func generateIndexTsContent(folders []string) string {
|
||||
builder := &strings.Builder{}
|
||||
builder.WriteString("export default null;\n")
|
||||
// Line 2:1: Delete `⏎` prettier/prettier
|
||||
if len(folders) > 0 {
|
||||
builder.WriteString("\n")
|
||||
}
|
||||
for _, folder := range folders {
|
||||
builder.WriteString(fmt.Sprintf("export { default as %s } from '%s';\n", folder, folder))
|
||||
}
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
// buildUI run pnpm install and pnpm build commands to build ui
|
||||
func buildUI(b *buildingMaterial) (err error) {
|
||||
localUIBuildDir := filepath.Join(b.tmpDir, "vendor/github.com/apache/answer/ui")
|
||||
@@ -362,13 +396,6 @@ func buildUI(b *buildingMaterial) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func replaceNecessaryFile(b *buildingMaterial) (err error) {
|
||||
fmt.Printf("try to replace ui build directory\n")
|
||||
uiBuildDir := filepath.Join(b.tmpDir, "vendor/github.com/apache/answer/ui")
|
||||
err = copyDirEntries(ui.Build, ".", uiBuildDir)
|
||||
return err
|
||||
}
|
||||
|
||||
// mergeI18nFiles merge i18n files
|
||||
func mergeI18nFiles(b *buildingMaterial) (err error) {
|
||||
fmt.Printf("try to merge i18n files\n")
|
||||
@@ -467,6 +494,13 @@ func copyDirEntries(sourceFs fs.FS, sourceDir, targetDir string, ignoreDir ...st
|
||||
if strings.HasPrefix(path, s) {
|
||||
return true
|
||||
}
|
||||
// Also ignore nested occurrences, e.g. src/plugins/foo/node_modules
|
||||
if strings.Contains(path, string(filepath.Separator)+s) {
|
||||
return true
|
||||
}
|
||||
if strings.Contains(path, "/"+s) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/apache/answer/internal/base/constant"
|
||||
"github.com/apache/answer/internal/base/data"
|
||||
"github.com/apache/answer/internal/entity"
|
||||
@@ -41,7 +42,9 @@ func SetDefaultConfig(dbConf *data.Database, cacheConf *data.CacheConf, field *C
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
defer func() {
|
||||
_ = db.Close()
|
||||
}()
|
||||
|
||||
cache, cacheCleanup, err := data.NewCache(cacheConf)
|
||||
if err != nil {
|
||||
@@ -49,7 +52,7 @@ func SetDefaultConfig(dbConf *data.Database, cacheConf *data.CacheConf, field *C
|
||||
}
|
||||
defer func() {
|
||||
if cache != nil {
|
||||
cache.Flush(context.Background())
|
||||
_ = cache.Flush(context.Background())
|
||||
cacheCleanup()
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -34,7 +34,9 @@ func DumpAllData(dataConf *data.Database, dumpDataPath string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
defer func() {
|
||||
_ = db.Close()
|
||||
}()
|
||||
if err = db.Ping(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -21,13 +21,14 @@ package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/apache/answer/i18n"
|
||||
"github.com/apache/answer/pkg/dir"
|
||||
"github.com/apache/answer/pkg/writer"
|
||||
"gopkg.in/yaml.v3"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type YamlPluginContent struct {
|
||||
|
||||
@@ -43,7 +43,9 @@ func CheckDBConnection(dataConf *data.Database) bool {
|
||||
fmt.Printf("connection database failed: %s\n", err)
|
||||
return false
|
||||
}
|
||||
defer db.Close()
|
||||
defer func() {
|
||||
_ = db.Close()
|
||||
}()
|
||||
if err = db.Ping(); err != nil {
|
||||
fmt.Printf("connection ping database failed: %s\n", err)
|
||||
return false
|
||||
@@ -59,7 +61,9 @@ func CheckDBTableExist(dataConf *data.Database) bool {
|
||||
fmt.Printf("connection database failed: %s\n", err)
|
||||
return false
|
||||
}
|
||||
defer db.Close()
|
||||
defer func() {
|
||||
_ = db.Close()
|
||||
}()
|
||||
if err = db.Ping(); err != nil {
|
||||
fmt.Printf("connection ping database failed: %s\n", err)
|
||||
return false
|
||||
|
||||
@@ -32,6 +32,7 @@ import (
|
||||
"github.com/apache/answer/internal/base/conf"
|
||||
"github.com/apache/answer/internal/base/data"
|
||||
"github.com/apache/answer/internal/base/path"
|
||||
"github.com/apache/answer/internal/repo/api_key"
|
||||
"github.com/apache/answer/internal/repo/auth"
|
||||
"github.com/apache/answer/internal/repo/user"
|
||||
authService "github.com/apache/answer/internal/service/auth"
|
||||
@@ -77,7 +78,9 @@ func ResetPassword(ctx context.Context, dataDirPath string, opts *ResetPasswordO
|
||||
if err != nil {
|
||||
return fmt.Errorf("connect database failed: %w", err)
|
||||
}
|
||||
defer db.Close()
|
||||
defer func() {
|
||||
_ = db.Close()
|
||||
}()
|
||||
|
||||
cache, cacheCleanup, err := data.NewCache(config.Data.Cache)
|
||||
if err != nil {
|
||||
@@ -93,7 +96,8 @@ func ResetPassword(ctx context.Context, dataDirPath string, opts *ResetPasswordO
|
||||
|
||||
userRepo := user.NewUserRepo(dataData)
|
||||
authRepo := auth.NewAuthRepo(dataData)
|
||||
authSvc := authService.NewAuthService(authRepo)
|
||||
apiKeyRepo := api_key.NewAPIKeyRepo(dataData)
|
||||
authSvc := authService.NewAuthService(authRepo, apiKeyRepo)
|
||||
|
||||
email := strings.TrimSpace(opts.Email)
|
||||
if email == "" {
|
||||
|
||||
@@ -58,6 +58,7 @@ func (ac *ActivityController) GetObjectTimeline(ctx *gin.Context) {
|
||||
req.ObjectID = uid.DeShortID(req.ObjectID)
|
||||
|
||||
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||
req.IsAdminModerator = middleware.GetUserIsAdminModerator(ctx)
|
||||
if userInfo := middleware.GetUserInfoFromContext(ctx); userInfo != nil {
|
||||
req.IsAdmin = userInfo.RoleID == role.RoleAdminID
|
||||
}
|
||||
@@ -81,6 +82,7 @@ func (ac *ActivityController) GetObjectTimelineDetail(ctx *gin.Context) {
|
||||
}
|
||||
|
||||
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||
req.IsAdminModerator = middleware.GetUserIsAdminModerator(ctx)
|
||||
|
||||
resp, err := ac.activityService.GetObjectTimelineDetail(ctx, req)
|
||||
handler.HandleResponse(ctx, err, resp)
|
||||
|
||||
@@ -0,0 +1,793 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"maps"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/apache/answer/internal/base/constant"
|
||||
"github.com/apache/answer/internal/base/handler"
|
||||
"github.com/apache/answer/internal/base/middleware"
|
||||
"github.com/apache/answer/internal/schema"
|
||||
"github.com/apache/answer/internal/schema/mcp_tools"
|
||||
"github.com/apache/answer/internal/service/ai_conversation"
|
||||
answercommon "github.com/apache/answer/internal/service/answer_common"
|
||||
"github.com/apache/answer/internal/service/comment"
|
||||
"github.com/apache/answer/internal/service/content"
|
||||
"github.com/apache/answer/internal/service/feature_toggle"
|
||||
questioncommon "github.com/apache/answer/internal/service/question_common"
|
||||
"github.com/apache/answer/internal/service/siteinfo_common"
|
||||
tagcommonser "github.com/apache/answer/internal/service/tag_common"
|
||||
usercommon "github.com/apache/answer/internal/service/user_common"
|
||||
"github.com/apache/answer/pkg/token"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"github.com/sashabaranov/go-openai"
|
||||
"github.com/segmentfault/pacman/errors"
|
||||
"github.com/segmentfault/pacman/i18n"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
)
|
||||
|
||||
type AIController struct {
|
||||
searchService *content.SearchService
|
||||
siteInfoService siteinfo_common.SiteInfoCommonService
|
||||
tagCommonService *tagcommonser.TagCommonService
|
||||
questioncommon *questioncommon.QuestionCommon
|
||||
commentRepo comment.CommentRepo
|
||||
userCommon *usercommon.UserCommon
|
||||
answerRepo answercommon.AnswerRepo
|
||||
mcpController *MCPController
|
||||
aiConversationService ai_conversation.AIConversationService
|
||||
featureToggleSvc *feature_toggle.FeatureToggleService
|
||||
}
|
||||
|
||||
// NewAIController new site info controller.
|
||||
func NewAIController(
|
||||
searchService *content.SearchService,
|
||||
siteInfoService siteinfo_common.SiteInfoCommonService,
|
||||
tagCommonService *tagcommonser.TagCommonService,
|
||||
questioncommon *questioncommon.QuestionCommon,
|
||||
commentRepo comment.CommentRepo,
|
||||
userCommon *usercommon.UserCommon,
|
||||
answerRepo answercommon.AnswerRepo,
|
||||
mcpController *MCPController,
|
||||
aiConversationService ai_conversation.AIConversationService,
|
||||
featureToggleSvc *feature_toggle.FeatureToggleService,
|
||||
) *AIController {
|
||||
return &AIController{
|
||||
searchService: searchService,
|
||||
siteInfoService: siteInfoService,
|
||||
tagCommonService: tagCommonService,
|
||||
questioncommon: questioncommon,
|
||||
commentRepo: commentRepo,
|
||||
userCommon: userCommon,
|
||||
answerRepo: answerRepo,
|
||||
mcpController: mcpController,
|
||||
aiConversationService: aiConversationService,
|
||||
featureToggleSvc: featureToggleSvc,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *AIController) ensureAIChatEnabled(ctx *gin.Context) bool {
|
||||
if c.featureToggleSvc == nil {
|
||||
return true
|
||||
}
|
||||
if err := c.featureToggleSvc.EnsureEnabled(ctx, feature_toggle.FeatureAIChatbot); err != nil {
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type ChatCompletionsRequest struct {
|
||||
Messages []Message `validate:"required,gte=1" json:"messages"`
|
||||
ConversationID string `json:"conversation_id"`
|
||||
UserID string `json:"-"`
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
Role string `json:"role" binding:"required"`
|
||||
Content string `json:"content" binding:"required"`
|
||||
}
|
||||
|
||||
type ChatCompletionsResponse struct {
|
||||
ID string `json:"id"`
|
||||
Object string `json:"object"`
|
||||
Created int64 `json:"created"`
|
||||
Model string `json:"model"`
|
||||
Choices []Choice `json:"choices"`
|
||||
Usage Usage `json:"usage"`
|
||||
}
|
||||
|
||||
type StreamResponse struct {
|
||||
ChatCompletionID string `json:"chat_completion_id"`
|
||||
Object string `json:"object"`
|
||||
Created int64 `json:"created"`
|
||||
Model string `json:"model"`
|
||||
Choices []StreamChoice `json:"choices"`
|
||||
}
|
||||
|
||||
type Choice struct {
|
||||
Index int `json:"index"`
|
||||
Message Message `json:"message"`
|
||||
FinishReason string `json:"finish_reason"`
|
||||
}
|
||||
|
||||
type StreamChoice struct {
|
||||
Index int `json:"index"`
|
||||
Delta Delta `json:"delta"`
|
||||
FinishReason *string `json:"finish_reason"`
|
||||
}
|
||||
|
||||
type Delta struct {
|
||||
Role string `json:"role,omitempty"`
|
||||
Content string `json:"content,omitempty"`
|
||||
ReasoningContent string `json:"reasoning_content,omitempty"`
|
||||
}
|
||||
|
||||
type Usage struct {
|
||||
PromptTokens int `json:"prompt_tokens"`
|
||||
CompletionTokens int `json:"completion_tokens"`
|
||||
TotalTokens int `json:"total_tokens"`
|
||||
}
|
||||
|
||||
type ConversationContext struct {
|
||||
ConversationID string
|
||||
UserID string
|
||||
UserQuestion string
|
||||
Messages []*ai_conversation.ConversationMessage
|
||||
IsNewConversation bool
|
||||
Model string
|
||||
}
|
||||
|
||||
func (c *ConversationContext) GetOpenAIMessages() []openai.ChatCompletionMessage {
|
||||
messages := make([]openai.ChatCompletionMessage, len(c.Messages))
|
||||
for i, msg := range c.Messages {
|
||||
messages[i] = openai.ChatCompletionMessage{
|
||||
Role: msg.Role,
|
||||
Content: msg.Content,
|
||||
}
|
||||
}
|
||||
return messages
|
||||
}
|
||||
|
||||
// sendStreamData
|
||||
func sendStreamData(w http.ResponseWriter, data StreamResponse) {
|
||||
jsonData, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintf(w, "data: %s\n\n", string(jsonData))
|
||||
if f, ok := w.(http.Flusher); ok {
|
||||
f.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *AIController) ChatCompletions(ctx *gin.Context) {
|
||||
if !c.ensureAIChatEnabled(ctx) {
|
||||
return
|
||||
}
|
||||
aiConfig, err := c.siteInfoService.GetSiteAI(context.Background())
|
||||
if err != nil {
|
||||
log.Errorf("Failed to get AI config: %v", err)
|
||||
handler.HandleResponse(ctx, errors.BadRequest("AI service configuration error"), nil)
|
||||
return
|
||||
}
|
||||
|
||||
if !aiConfig.Enabled {
|
||||
handler.HandleResponse(ctx, errors.ServiceUnavailable("AI service is not enabled"), nil)
|
||||
return
|
||||
}
|
||||
|
||||
aiProvider := aiConfig.GetProvider()
|
||||
|
||||
req := &ChatCompletionsRequest{}
|
||||
if handler.BindAndCheck(ctx, req) {
|
||||
return
|
||||
}
|
||||
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||
|
||||
data, _ := json.Marshal(req)
|
||||
log.Infof("ai chat request data: %s", string(data))
|
||||
|
||||
ctx.Header("Content-Type", "text/event-stream")
|
||||
ctx.Header("Cache-Control", "no-cache")
|
||||
ctx.Header("Connection", "keep-alive")
|
||||
ctx.Header("Access-Control-Allow-Origin", "*")
|
||||
ctx.Header("Access-Control-Allow-Headers", "Cache-Control")
|
||||
|
||||
ctx.Status(http.StatusOK)
|
||||
|
||||
w := ctx.Writer
|
||||
|
||||
if f, ok := w.(http.Flusher); ok {
|
||||
f.Flush()
|
||||
}
|
||||
|
||||
chatcmplID := "chatcmpl-" + token.GenerateToken()
|
||||
created := time.Now().Unix()
|
||||
|
||||
firstResponse := StreamResponse{
|
||||
ChatCompletionID: chatcmplID,
|
||||
Object: "chat.completion.chunk",
|
||||
Created: time.Now().Unix(),
|
||||
Model: aiProvider.Model,
|
||||
Choices: []StreamChoice{{Index: 0, Delta: Delta{Role: "assistant"}, FinishReason: nil}},
|
||||
}
|
||||
|
||||
sendStreamData(w, firstResponse)
|
||||
|
||||
conversationCtx := c.initializeConversationContext(ctx, aiProvider.Model, req)
|
||||
if conversationCtx == nil {
|
||||
log.Error("Failed to initialize conversation context")
|
||||
c.sendErrorResponse(w, chatcmplID, aiProvider.Model, "Failed to initialize conversation context")
|
||||
return
|
||||
}
|
||||
|
||||
c.redirectRequestToAI(ctx, w, chatcmplID, conversationCtx)
|
||||
|
||||
finishReason := "stop"
|
||||
endResponse := StreamResponse{
|
||||
ChatCompletionID: chatcmplID,
|
||||
Object: "chat.completion.chunk",
|
||||
Created: created,
|
||||
Model: aiProvider.Model,
|
||||
Choices: []StreamChoice{{Index: 0, Delta: Delta{}, FinishReason: &finishReason}},
|
||||
}
|
||||
|
||||
sendStreamData(w, endResponse)
|
||||
|
||||
_, _ = fmt.Fprintf(w, "data: [DONE]\n\n")
|
||||
if f, ok := w.(http.Flusher); ok {
|
||||
f.Flush()
|
||||
}
|
||||
|
||||
c.saveConversationRecord(ctx, chatcmplID, conversationCtx)
|
||||
}
|
||||
|
||||
func (c *AIController) redirectRequestToAI(ctx *gin.Context, w http.ResponseWriter, id string, conversationCtx *ConversationContext) {
|
||||
client := c.createOpenAIClient()
|
||||
|
||||
c.handleAIConversation(ctx, w, id, client, conversationCtx)
|
||||
}
|
||||
|
||||
// createOpenAIClient
|
||||
func (c *AIController) createOpenAIClient() *openai.Client {
|
||||
config := openai.DefaultConfig("")
|
||||
config.BaseURL = ""
|
||||
|
||||
aiConfig, err := c.siteInfoService.GetSiteAI(context.Background())
|
||||
if err != nil {
|
||||
log.Errorf("Failed to get AI config: %v", err)
|
||||
return openai.NewClientWithConfig(config)
|
||||
}
|
||||
|
||||
if !aiConfig.Enabled {
|
||||
log.Warn("AI feature is disabled")
|
||||
return openai.NewClientWithConfig(config)
|
||||
}
|
||||
|
||||
aiProvider := aiConfig.GetProvider()
|
||||
|
||||
config = openai.DefaultConfig(aiProvider.APIKey)
|
||||
config.BaseURL = aiProvider.APIHost
|
||||
if !strings.HasSuffix(config.BaseURL, "/v1") {
|
||||
config.BaseURL += "/v1"
|
||||
}
|
||||
return openai.NewClientWithConfig(config)
|
||||
}
|
||||
|
||||
// getPromptByLanguage
|
||||
func (c *AIController) getPromptByLanguage(language i18n.Language, question string) string {
|
||||
aiConfig, err := c.siteInfoService.GetSiteAI(context.Background())
|
||||
if err != nil {
|
||||
log.Errorf("Failed to get AI config: %v", err)
|
||||
return c.getDefaultPrompt(language, question)
|
||||
}
|
||||
|
||||
var promptTemplate string
|
||||
|
||||
switch language {
|
||||
case i18n.LanguageChinese:
|
||||
promptTemplate = aiConfig.PromptConfig.ZhCN
|
||||
case i18n.LanguageEnglish:
|
||||
promptTemplate = aiConfig.PromptConfig.EnUS
|
||||
default:
|
||||
promptTemplate = aiConfig.PromptConfig.EnUS
|
||||
}
|
||||
|
||||
if promptTemplate == "" {
|
||||
return c.getDefaultPrompt(language, question)
|
||||
}
|
||||
|
||||
return fmt.Sprintf(promptTemplate, question)
|
||||
}
|
||||
|
||||
// getDefaultPrompt prompt
|
||||
func (c *AIController) getDefaultPrompt(language i18n.Language, question string) string {
|
||||
switch language {
|
||||
case i18n.LanguageChinese:
|
||||
return fmt.Sprintf(constant.DefaultAIPromptConfigZhCN, question)
|
||||
case i18n.LanguageEnglish:
|
||||
return fmt.Sprintf(constant.DefaultAIPromptConfigEnUS, question)
|
||||
default:
|
||||
return fmt.Sprintf(constant.DefaultAIPromptConfigEnUS, question)
|
||||
}
|
||||
}
|
||||
|
||||
// initializeConversationContext
|
||||
func (c *AIController) initializeConversationContext(ctx *gin.Context, model string, req *ChatCompletionsRequest) *ConversationContext {
|
||||
if len(req.ConversationID) == 0 {
|
||||
req.ConversationID = token.GenerateToken()
|
||||
}
|
||||
conversationCtx := &ConversationContext{
|
||||
UserID: req.UserID,
|
||||
Messages: make([]*ai_conversation.ConversationMessage, 0),
|
||||
ConversationID: req.ConversationID,
|
||||
Model: model,
|
||||
}
|
||||
|
||||
conversationDetail, exist, err := c.aiConversationService.GetConversationDetail(ctx, &schema.AIConversationDetailReq{
|
||||
ConversationID: req.ConversationID,
|
||||
UserID: req.UserID,
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("Failed to get conversation detail: %v", err)
|
||||
return nil
|
||||
}
|
||||
if !exist {
|
||||
conversationCtx.UserQuestion = req.Messages[0].Content
|
||||
conversationCtx.Messages = c.buildInitialMessages(ctx, req)
|
||||
conversationCtx.IsNewConversation = true
|
||||
return conversationCtx
|
||||
}
|
||||
conversationCtx.IsNewConversation = false
|
||||
|
||||
for _, record := range conversationDetail.Records {
|
||||
conversationCtx.Messages = append(conversationCtx.Messages, &ai_conversation.ConversationMessage{
|
||||
ChatCompletionID: record.ChatCompletionID,
|
||||
Role: record.Role,
|
||||
Content: record.Content,
|
||||
})
|
||||
}
|
||||
conversationCtx.Messages = append(conversationCtx.Messages, &ai_conversation.ConversationMessage{
|
||||
Role: req.Messages[0].Role,
|
||||
Content: req.Messages[0].Content,
|
||||
})
|
||||
return conversationCtx
|
||||
}
|
||||
|
||||
// buildInitialMessages
|
||||
func (c *AIController) buildInitialMessages(ctx *gin.Context, req *ChatCompletionsRequest) []*ai_conversation.ConversationMessage {
|
||||
question := ""
|
||||
if len(req.Messages) == 1 {
|
||||
question = req.Messages[0].Content
|
||||
} else {
|
||||
messages := make([]*ai_conversation.ConversationMessage, len(req.Messages))
|
||||
for i, msg := range req.Messages {
|
||||
messages[i] = &ai_conversation.ConversationMessage{
|
||||
Role: msg.Role,
|
||||
Content: msg.Content,
|
||||
}
|
||||
}
|
||||
return messages
|
||||
}
|
||||
|
||||
currentLang := handler.GetLangByCtx(ctx)
|
||||
|
||||
prompt := c.getPromptByLanguage(currentLang, question)
|
||||
|
||||
return []*ai_conversation.ConversationMessage{{Role: openai.ChatMessageRoleUser, Content: prompt}}
|
||||
}
|
||||
|
||||
// saveConversationRecord
|
||||
func (c *AIController) saveConversationRecord(ctx context.Context, chatcmplID string, conversationCtx *ConversationContext) {
|
||||
if conversationCtx == nil || len(conversationCtx.Messages) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if conversationCtx.IsNewConversation {
|
||||
topic := conversationCtx.UserQuestion
|
||||
if topic == "" {
|
||||
log.Warn("No user message found for new conversation")
|
||||
return
|
||||
}
|
||||
|
||||
err := c.aiConversationService.CreateConversation(ctx, conversationCtx.UserID, conversationCtx.ConversationID, topic)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to create conversation: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err := c.aiConversationService.SaveConversationRecords(ctx, conversationCtx.ConversationID, chatcmplID, conversationCtx.Messages)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to save conversation records: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *AIController) handleAIConversation(ctx *gin.Context, w http.ResponseWriter, id string, client *openai.Client, conversationCtx *ConversationContext) {
|
||||
maxRounds := 10
|
||||
messages := conversationCtx.GetOpenAIMessages()
|
||||
|
||||
for round := range maxRounds {
|
||||
log.Debugf("AI conversation round: %d", round+1)
|
||||
|
||||
aiReq := openai.ChatCompletionRequest{
|
||||
Model: conversationCtx.Model,
|
||||
Messages: messages,
|
||||
Tools: c.getMCPTools(),
|
||||
Stream: true,
|
||||
}
|
||||
|
||||
toolCalls, newMessages, finished, aiResponse, reasoningContent := c.processAIStream(ctx, w, id, conversationCtx.Model, client, aiReq, messages)
|
||||
messages = newMessages
|
||||
|
||||
log.Debugf("Round %d: toolCalls=%v", round+1, toolCalls)
|
||||
if aiResponse != "" || reasoningContent != "" {
|
||||
conversationCtx.Messages = append(conversationCtx.Messages, &ai_conversation.ConversationMessage{
|
||||
Role: "assistant",
|
||||
Content: aiResponse,
|
||||
ReasoningContent: reasoningContent,
|
||||
})
|
||||
}
|
||||
|
||||
if finished {
|
||||
return
|
||||
}
|
||||
|
||||
if len(toolCalls) > 0 {
|
||||
messages = c.executeToolCalls(ctx, w, id, conversationCtx.Model, toolCalls, messages, aiResponse, reasoningContent)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
log.Warnf("AI conversation reached maximum rounds limit: %d", maxRounds)
|
||||
}
|
||||
|
||||
// processAIStream
|
||||
func (c *AIController) processAIStream(
|
||||
_ *gin.Context, w http.ResponseWriter, id, model string, client *openai.Client, aiReq openai.ChatCompletionRequest, messages []openai.ChatCompletionMessage) (
|
||||
[]openai.ToolCall, []openai.ChatCompletionMessage, bool, string, string) {
|
||||
stream, err := client.CreateChatCompletionStream(context.Background(), aiReq)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to create stream: %v", err)
|
||||
c.sendErrorResponse(w, id, model, "Failed to create AI stream")
|
||||
return nil, messages, true, "", ""
|
||||
}
|
||||
defer func() {
|
||||
_ = stream.Close()
|
||||
}()
|
||||
|
||||
var currentToolCalls []openai.ToolCall
|
||||
var accumulatedContent strings.Builder
|
||||
var accumulatedReasoning strings.Builder
|
||||
var accumulatedMessage openai.ChatCompletionMessage
|
||||
toolCallsMap := make(map[int]*openai.ToolCall)
|
||||
|
||||
for {
|
||||
response, err := stream.Recv()
|
||||
if err != nil {
|
||||
if err.Error() == "EOF" {
|
||||
log.Info("Stream finished")
|
||||
break
|
||||
}
|
||||
log.Errorf("Stream error: %v", err)
|
||||
break
|
||||
}
|
||||
|
||||
if len(response.Choices) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
choice := response.Choices[0]
|
||||
|
||||
if len(choice.Delta.ToolCalls) > 0 {
|
||||
for _, deltaToolCall := range choice.Delta.ToolCalls {
|
||||
index := *deltaToolCall.Index
|
||||
|
||||
if _, exists := toolCallsMap[index]; !exists {
|
||||
toolCallsMap[index] = &openai.ToolCall{
|
||||
ID: deltaToolCall.ID,
|
||||
Type: deltaToolCall.Type,
|
||||
Function: openai.FunctionCall{
|
||||
Name: deltaToolCall.Function.Name,
|
||||
Arguments: deltaToolCall.Function.Arguments,
|
||||
},
|
||||
}
|
||||
} else {
|
||||
if deltaToolCall.Function.Arguments != "" {
|
||||
toolCallsMap[index].Function.Arguments += deltaToolCall.Function.Arguments
|
||||
}
|
||||
if deltaToolCall.Function.Name != "" {
|
||||
toolCallsMap[index].Function.Name = deltaToolCall.Function.Name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if choice.Delta.ReasoningContent != "" {
|
||||
accumulatedReasoning.WriteString(choice.Delta.ReasoningContent)
|
||||
|
||||
reasoningResponse := StreamResponse{
|
||||
ChatCompletionID: id,
|
||||
Object: "chat.completion.chunk",
|
||||
Created: time.Now().Unix(),
|
||||
Model: model,
|
||||
Choices: []StreamChoice{
|
||||
{
|
||||
Index: 0,
|
||||
Delta: Delta{
|
||||
ReasoningContent: choice.Delta.ReasoningContent,
|
||||
},
|
||||
FinishReason: nil,
|
||||
},
|
||||
},
|
||||
}
|
||||
sendStreamData(w, reasoningResponse)
|
||||
}
|
||||
|
||||
if choice.Delta.Content != "" {
|
||||
accumulatedContent.WriteString(choice.Delta.Content)
|
||||
|
||||
contentResponse := StreamResponse{
|
||||
ChatCompletionID: id,
|
||||
Object: "chat.completion.chunk",
|
||||
Created: time.Now().Unix(),
|
||||
Model: model,
|
||||
Choices: []StreamChoice{
|
||||
{
|
||||
Index: 0,
|
||||
Delta: Delta{
|
||||
Content: choice.Delta.Content,
|
||||
},
|
||||
FinishReason: nil,
|
||||
},
|
||||
},
|
||||
}
|
||||
sendStreamData(w, contentResponse)
|
||||
}
|
||||
|
||||
if len(choice.FinishReason) > 0 {
|
||||
if choice.FinishReason == "tool_calls" {
|
||||
for _, toolCall := range toolCallsMap {
|
||||
currentToolCalls = append(currentToolCalls, *toolCall)
|
||||
}
|
||||
return currentToolCalls, messages, false, accumulatedContent.String(), accumulatedReasoning.String()
|
||||
} else {
|
||||
aiResponseContent := accumulatedContent.String()
|
||||
aiReasoningContent := accumulatedReasoning.String()
|
||||
if aiResponseContent != "" || aiReasoningContent != "" {
|
||||
accumulatedMessage = openai.ChatCompletionMessage{
|
||||
Role: openai.ChatMessageRoleAssistant,
|
||||
Content: aiResponseContent,
|
||||
ReasoningContent: aiReasoningContent,
|
||||
}
|
||||
messages = append(messages, accumulatedMessage)
|
||||
}
|
||||
return nil, messages, true, aiResponseContent, aiReasoningContent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
aiResponseContent := accumulatedContent.String()
|
||||
aiReasoningContent := accumulatedReasoning.String()
|
||||
if aiResponseContent != "" || aiReasoningContent != "" {
|
||||
accumulatedMessage = openai.ChatCompletionMessage{
|
||||
Role: openai.ChatMessageRoleAssistant,
|
||||
Content: aiResponseContent,
|
||||
ReasoningContent: aiReasoningContent,
|
||||
}
|
||||
messages = append(messages, accumulatedMessage)
|
||||
}
|
||||
|
||||
if len(toolCallsMap) > 0 {
|
||||
for _, toolCall := range toolCallsMap {
|
||||
currentToolCalls = append(currentToolCalls, *toolCall)
|
||||
}
|
||||
return currentToolCalls, messages, false, aiResponseContent, aiReasoningContent
|
||||
}
|
||||
|
||||
return currentToolCalls, messages, len(currentToolCalls) == 0, aiResponseContent, aiReasoningContent
|
||||
}
|
||||
|
||||
// executeToolCalls
|
||||
func (c *AIController) executeToolCalls(ctx *gin.Context, _ http.ResponseWriter, _, _ string, toolCalls []openai.ToolCall, messages []openai.ChatCompletionMessage, assistantContent, reasoningContent string) []openai.ChatCompletionMessage {
|
||||
validToolCalls := make([]openai.ToolCall, 0)
|
||||
for _, toolCall := range toolCalls {
|
||||
if toolCall.ID == "" || toolCall.Function.Name == "" {
|
||||
log.Errorf("Invalid tool call: missing required fields. ID: %s, Function: %v", toolCall.ID, toolCall.Function)
|
||||
continue
|
||||
}
|
||||
|
||||
if toolCall.Function.Arguments == "" {
|
||||
toolCall.Function.Arguments = "{}"
|
||||
}
|
||||
|
||||
validToolCalls = append(validToolCalls, toolCall)
|
||||
log.Debugf("Valid tool call: ID=%s, Name=%s, Arguments=%s", toolCall.ID, toolCall.Function.Name, toolCall.Function.Arguments)
|
||||
}
|
||||
|
||||
if len(validToolCalls) == 0 {
|
||||
log.Warn("No valid tool calls found")
|
||||
return messages
|
||||
}
|
||||
|
||||
assistantMsg := openai.ChatCompletionMessage{
|
||||
Role: openai.ChatMessageRoleAssistant,
|
||||
Content: assistantContent,
|
||||
ReasoningContent: reasoningContent,
|
||||
ToolCalls: validToolCalls,
|
||||
}
|
||||
messages = append(messages, assistantMsg)
|
||||
|
||||
for _, toolCall := range validToolCalls {
|
||||
if toolCall.Function.Name != "" {
|
||||
var args map[string]any
|
||||
if err := json.Unmarshal([]byte(toolCall.Function.Arguments), &args); err != nil {
|
||||
log.Errorf("Failed to parse tool arguments for %s: %v, arguments: %s", toolCall.Function.Name, err, toolCall.Function.Arguments)
|
||||
errorResult := fmt.Sprintf("Error parsing tool arguments: %v", err)
|
||||
toolMessage := openai.ChatCompletionMessage{
|
||||
Role: openai.ChatMessageRoleTool,
|
||||
Content: errorResult,
|
||||
ToolCallID: toolCall.ID,
|
||||
}
|
||||
messages = append(messages, toolMessage)
|
||||
continue
|
||||
}
|
||||
|
||||
result, err := c.callMCPTool(ctx, toolCall.Function.Name, args)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to call MCP tool %s: %v", toolCall.Function.Name, err)
|
||||
result = fmt.Sprintf("Error calling tool %s: %v", toolCall.Function.Name, err)
|
||||
}
|
||||
|
||||
toolMessage := openai.ChatCompletionMessage{
|
||||
Role: openai.ChatMessageRoleTool,
|
||||
Content: result,
|
||||
ToolCallID: toolCall.ID,
|
||||
}
|
||||
messages = append(messages, toolMessage)
|
||||
}
|
||||
}
|
||||
|
||||
return messages
|
||||
}
|
||||
|
||||
// sendErrorResponse send error response in stream
|
||||
func (c *AIController) sendErrorResponse(w http.ResponseWriter, id, model, errorMsg string) {
|
||||
errorResponse := StreamResponse{
|
||||
ChatCompletionID: id,
|
||||
Object: "chat.completion.chunk",
|
||||
Created: time.Now().Unix(),
|
||||
Model: model,
|
||||
Choices: []StreamChoice{
|
||||
{
|
||||
Index: 0,
|
||||
Delta: Delta{
|
||||
Content: fmt.Sprintf("Error: %s", errorMsg),
|
||||
},
|
||||
FinishReason: nil,
|
||||
},
|
||||
},
|
||||
}
|
||||
sendStreamData(w, errorResponse)
|
||||
}
|
||||
|
||||
// getMCPTools
|
||||
func (c *AIController) getMCPTools() []openai.Tool {
|
||||
openaiTools := make([]openai.Tool, 0)
|
||||
for _, mcpTool := range mcp_tools.MCPToolsList {
|
||||
openaiTool := c.convertMCPToolToOpenAI(mcpTool)
|
||||
openaiTools = append(openaiTools, openaiTool)
|
||||
}
|
||||
|
||||
return openaiTools
|
||||
}
|
||||
|
||||
// convertMCPToolToOpenAI
|
||||
func (c *AIController) convertMCPToolToOpenAI(mcpTool mcp.Tool) openai.Tool {
|
||||
properties := make(map[string]any)
|
||||
required := make([]string, 0)
|
||||
|
||||
maps.Copy(properties, mcpTool.InputSchema.Properties)
|
||||
|
||||
required = append(required, mcpTool.InputSchema.Required...)
|
||||
|
||||
parameters := map[string]any{
|
||||
"type": "object",
|
||||
"properties": properties,
|
||||
}
|
||||
|
||||
if len(required) > 0 {
|
||||
parameters["required"] = required
|
||||
}
|
||||
|
||||
return openai.Tool{
|
||||
Type: openai.ToolTypeFunction,
|
||||
Function: &openai.FunctionDefinition{
|
||||
Name: mcpTool.Name,
|
||||
Description: mcpTool.Description,
|
||||
Parameters: parameters,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// callMCPTool
|
||||
func (c *AIController) callMCPTool(ctx context.Context, toolName string, arguments map[string]any) (string, error) {
|
||||
request := mcp.CallToolRequest{
|
||||
Request: mcp.Request{},
|
||||
Params: struct {
|
||||
Name string `json:"name"`
|
||||
Arguments any `json:"arguments,omitempty"`
|
||||
Meta *mcp.Meta `json:"_meta,omitempty"`
|
||||
}{
|
||||
Name: toolName,
|
||||
Arguments: arguments,
|
||||
},
|
||||
}
|
||||
|
||||
var result *mcp.CallToolResult
|
||||
var err error
|
||||
|
||||
log.Debugf("Calling MCP tool: %s with arguments: %v", toolName, arguments)
|
||||
|
||||
switch toolName {
|
||||
case "get_questions":
|
||||
result, err = c.mcpController.MCPQuestionsHandler()(ctx, request)
|
||||
case "get_answers_by_question_id":
|
||||
result, err = c.mcpController.MCPAnswersHandler()(ctx, request)
|
||||
case "get_comments":
|
||||
result, err = c.mcpController.MCPCommentsHandler()(ctx, request)
|
||||
case "get_tags":
|
||||
result, err = c.mcpController.MCPTagsHandler()(ctx, request)
|
||||
case "get_tag_detail":
|
||||
result, err = c.mcpController.MCPTagDetailsHandler()(ctx, request)
|
||||
case "get_user":
|
||||
result, err = c.mcpController.MCPUserDetailsHandler()(ctx, request)
|
||||
case "semantic_search":
|
||||
result, err = c.mcpController.MCPSemanticSearchHandler()(ctx, request)
|
||||
default:
|
||||
return "", fmt.Errorf("unknown tool: %s", toolName)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
data, _ := json.Marshal(result)
|
||||
log.Debugf("MCP tool %s called successfully, result: %v", toolName, string(data))
|
||||
|
||||
if result != nil && len(result.Content) > 0 {
|
||||
if textContent, ok := result.Content[0].(mcp.TextContent); ok {
|
||||
return textContent.Text, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "No result found", nil
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package controller
|
||||
|
||||
import (
|
||||
"github.com/apache/answer/internal/base/handler"
|
||||
"github.com/apache/answer/internal/base/middleware"
|
||||
"github.com/apache/answer/internal/schema"
|
||||
"github.com/apache/answer/internal/service/ai_conversation"
|
||||
"github.com/apache/answer/internal/service/feature_toggle"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// AIConversationController ai conversation controller
|
||||
type AIConversationController struct {
|
||||
aiConversationService ai_conversation.AIConversationService
|
||||
featureToggleSvc *feature_toggle.FeatureToggleService
|
||||
}
|
||||
|
||||
// NewAIConversationController creates a new AI conversation controller
|
||||
func NewAIConversationController(
|
||||
aiConversationService ai_conversation.AIConversationService,
|
||||
featureToggleSvc *feature_toggle.FeatureToggleService,
|
||||
) *AIConversationController {
|
||||
return &AIConversationController{
|
||||
aiConversationService: aiConversationService,
|
||||
featureToggleSvc: featureToggleSvc,
|
||||
}
|
||||
}
|
||||
|
||||
func (ctrl *AIConversationController) ensureEnabled(ctx *gin.Context) bool {
|
||||
if ctrl.featureToggleSvc == nil {
|
||||
return true
|
||||
}
|
||||
if err := ctrl.featureToggleSvc.EnsureEnabled(ctx, feature_toggle.FeatureAIChatbot); err != nil {
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// GetConversationList gets conversation list
|
||||
// @Summary get conversation list
|
||||
// @Description get conversation list
|
||||
// @Tags ai-conversation
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param page query int false "page"
|
||||
// @Param page_size query int false "page size"
|
||||
// @Success 200 {object} handler.RespBody{data=pager.PageModel{list=[]schema.AIConversationListItem}}
|
||||
// @Router /answer/api/v1/ai/conversation/page [get]
|
||||
func (ctrl *AIConversationController) GetConversationList(ctx *gin.Context) {
|
||||
if !ctrl.ensureEnabled(ctx) {
|
||||
return
|
||||
}
|
||||
req := &schema.AIConversationListReq{}
|
||||
if handler.BindAndCheck(ctx, req) {
|
||||
return
|
||||
}
|
||||
|
||||
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||
|
||||
resp, err := ctrl.aiConversationService.GetConversationList(ctx, req)
|
||||
handler.HandleResponse(ctx, err, resp)
|
||||
}
|
||||
|
||||
// GetConversationDetail gets conversation detail
|
||||
// @Summary get conversation detail
|
||||
// @Description get conversation detail
|
||||
// @Tags ai-conversation
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param conversation_id query string true "conversation id"
|
||||
// @Success 200 {object} handler.RespBody{data=schema.AIConversationDetailResp}
|
||||
// @Router /answer/api/v1/ai/conversation [get]
|
||||
func (ctrl *AIConversationController) GetConversationDetail(ctx *gin.Context) {
|
||||
if !ctrl.ensureEnabled(ctx) {
|
||||
return
|
||||
}
|
||||
req := &schema.AIConversationDetailReq{}
|
||||
if handler.BindAndCheck(ctx, req) {
|
||||
return
|
||||
}
|
||||
|
||||
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||
|
||||
resp, _, err := ctrl.aiConversationService.GetConversationDetail(ctx, req)
|
||||
handler.HandleResponse(ctx, err, resp)
|
||||
}
|
||||
|
||||
// VoteRecord vote record
|
||||
// @Summary vote record
|
||||
// @Description vote record
|
||||
// @Tags ai-conversation
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param data body schema.AIConversationVoteReq true "vote request"
|
||||
// @Success 200 {object} handler.RespBody
|
||||
// @Router /answer/api/v1/ai/conversation/vote [post]
|
||||
func (ctrl *AIConversationController) VoteRecord(ctx *gin.Context) {
|
||||
if !ctrl.ensureEnabled(ctx) {
|
||||
return
|
||||
}
|
||||
req := &schema.AIConversationVoteReq{}
|
||||
if handler.BindAndCheck(ctx, req) {
|
||||
return
|
||||
}
|
||||
|
||||
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||
|
||||
err := ctrl.aiConversationService.VoteRecord(ctx, req)
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
}
|
||||
@@ -89,7 +89,7 @@ func (ac *AnswerController) RemoveAnswer(ctx *gin.Context) {
|
||||
if !captchaPass {
|
||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||
ErrorField: "captcha_code",
|
||||
ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
|
||||
ErrorMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.CaptchaVerificationFailed),
|
||||
})
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
|
||||
return
|
||||
@@ -164,8 +164,9 @@ func (ac *AnswerController) GetAnswerInfo(ctx *gin.Context) {
|
||||
id := ctx.Query("id")
|
||||
id = uid.DeShortID(id)
|
||||
userID := middleware.GetLoginUserIDFromContext(ctx)
|
||||
isAdminModerator := middleware.GetUserIsAdminModerator(ctx)
|
||||
|
||||
info, questionInfo, has, err := ac.answerService.Get(ctx, id, userID)
|
||||
info, questionInfo, has, err := ac.answerService.Get(ctx, id, userID, isAdminModerator)
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, err, gin.H{})
|
||||
return
|
||||
@@ -225,7 +226,7 @@ func (ac *AnswerController) AddAnswer(ctx *gin.Context) {
|
||||
if !captchaPass {
|
||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||
ErrorField: "captcha_code",
|
||||
ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
|
||||
ErrorMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.CaptchaVerificationFailed),
|
||||
})
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
|
||||
return
|
||||
@@ -242,7 +243,7 @@ func (ac *AnswerController) AddAnswer(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
write, err := ac.siteInfoCommonService.GetSiteWrite(ctx)
|
||||
write, err := ac.siteInfoCommonService.GetSiteQuestion(ctx)
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
return
|
||||
@@ -271,7 +272,7 @@ func (ac *AnswerController) AddAnswer(ctx *gin.Context) {
|
||||
if !isAdmin || !linkUrlLimitUser {
|
||||
ac.actionService.ActionRecordAdd(ctx, entity.CaptchaActionAnswer, req.UserID)
|
||||
}
|
||||
info, questionInfo, has, err := ac.answerService.Get(ctx, answerID, req.UserID)
|
||||
info, questionInfo, has, err := ac.answerService.Get(ctx, answerID, req.UserID, isAdmin)
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
return
|
||||
@@ -318,7 +319,6 @@ func (ac *AnswerController) UpdateAnswer(ctx *gin.Context) {
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
return
|
||||
}
|
||||
req.QuestionID = uid.DeShortID(req.QuestionID)
|
||||
linkUrlLimitUser := canList[2]
|
||||
isAdmin := middleware.GetUserIsAdminModerator(ctx)
|
||||
if !isAdmin || !linkUrlLimitUser {
|
||||
@@ -326,7 +326,7 @@ func (ac *AnswerController) UpdateAnswer(ctx *gin.Context) {
|
||||
if !captchaPass {
|
||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||
ErrorField: "captcha_code",
|
||||
ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
|
||||
ErrorMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.CaptchaVerificationFailed),
|
||||
})
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
|
||||
return
|
||||
@@ -349,7 +349,7 @@ func (ac *AnswerController) UpdateAnswer(ctx *gin.Context) {
|
||||
if !isAdmin || !linkUrlLimitUser {
|
||||
ac.actionService.ActionRecordAdd(ctx, entity.CaptchaActionEdit, req.UserID)
|
||||
}
|
||||
_, _, _, err = ac.answerService.Get(ctx, req.ID, req.UserID)
|
||||
_, _, _, err = ac.answerService.Get(ctx, req.ID, req.UserID, isAdmin)
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
return
|
||||
@@ -377,6 +377,7 @@ func (ac *AnswerController) AnswerList(ctx *gin.Context) {
|
||||
|
||||
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||
req.QuestionID = uid.DeShortID(req.QuestionID)
|
||||
req.IsAdminModerator = middleware.GetUserIsAdminModerator(ctx)
|
||||
|
||||
canList, err := ac.rankService.CheckOperationPermissions(ctx, req.UserID, []string{
|
||||
permission.AnswerEdit,
|
||||
|
||||
@@ -106,7 +106,7 @@ func (cc *CommentController) AddComment(ctx *gin.Context) {
|
||||
if !captchaPass {
|
||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||
ErrorField: "captcha_code",
|
||||
ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
|
||||
ErrorMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.CaptchaVerificationFailed),
|
||||
})
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
|
||||
return
|
||||
@@ -154,7 +154,7 @@ func (cc *CommentController) RemoveComment(ctx *gin.Context) {
|
||||
if !captchaPass {
|
||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||
ErrorField: "captcha_code",
|
||||
ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
|
||||
ErrorMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.CaptchaVerificationFailed),
|
||||
})
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
|
||||
return
|
||||
@@ -215,7 +215,7 @@ func (cc *CommentController) UpdateComment(ctx *gin.Context) {
|
||||
if !captchaPass {
|
||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||
ErrorField: "captcha_code",
|
||||
ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
|
||||
ErrorMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.CaptchaVerificationFailed),
|
||||
})
|
||||
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
|
||||
return
|
||||
@@ -246,7 +246,9 @@ func (cc *CommentController) GetCommentWithPage(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
req.ObjectID = uid.DeShortID(req.ObjectID)
|
||||
req.CommentID = uid.DeShortID(req.CommentID)
|
||||
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||
req.IsAdminModerator = middleware.GetUserIsAdminModerator(ctx)
|
||||
canList, err := cc.rankService.CheckOperationPermissions(ctx, req.UserID, []string{
|
||||
permission.CommentEdit,
|
||||
permission.CommentDelete,
|
||||
@@ -299,6 +301,7 @@ func (cc *CommentController) GetComment(ctx *gin.Context) {
|
||||
}
|
||||
|
||||
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||
req.IsAdminModerator = middleware.GetUserIsAdminModerator(ctx)
|
||||
canList, err := cc.rankService.CheckOperationPermissions(ctx, req.UserID, []string{
|
||||
permission.CommentEdit,
|
||||
permission.CommentDelete,
|
||||
|
||||
@@ -22,6 +22,7 @@ package controller
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/apache/answer/internal/base/handler"
|
||||
"github.com/apache/answer/internal/base/middleware"
|
||||
@@ -106,6 +107,27 @@ func (cc *ConnectorController) ConnectorLogin(connector plugin.Connector) (fn fu
|
||||
return
|
||||
}
|
||||
|
||||
state := ctx.Query("state")
|
||||
if len(state) > 0 {
|
||||
stateInfo, err := cc.userExternalService.GetOAuthState(ctx, state)
|
||||
if err != nil || stateInfo == nil || stateInfo.Provider != connector.ConnectorSlugName() {
|
||||
log.Errorf("invalid connector oauth state for provider %s", connector.ConnectorSlugName())
|
||||
ctx.Redirect(http.StatusFound, "/50x")
|
||||
return
|
||||
}
|
||||
} else {
|
||||
state, err = cc.userExternalService.GenerateOAuthState(ctx, connector.ConnectorSlugName(),
|
||||
schema.ExternalLoginOAuthStateLoginIntent, "")
|
||||
if err != nil {
|
||||
log.Errorf("generate connector oauth state failed: %v", err)
|
||||
ctx.Redirect(http.StatusFound, "/50x")
|
||||
return
|
||||
}
|
||||
q := ctx.Request.URL.Query()
|
||||
q.Set("state", state)
|
||||
ctx.Request.URL.RawQuery = q.Encode()
|
||||
}
|
||||
|
||||
receiverURL := fmt.Sprintf("%s%s%s%s", general.SiteUrl,
|
||||
commonRouterPrefix, ConnectorRedirectRouterPrefix, connector.ConnectorSlugName())
|
||||
redirectURL := connector.ConnectorSender(ctx, receiverURL)
|
||||
@@ -141,6 +163,27 @@ func (cc *ConnectorController) ConnectorRedirect(connector plugin.Connector) (fn
|
||||
Avatar: userInfo.Avatar,
|
||||
MetaInfo: userInfo.MetaInfo,
|
||||
}
|
||||
stateInfo, err := cc.userExternalService.ConsumeOAuthState(ctx, ctx.Query("state"))
|
||||
if err != nil {
|
||||
log.Errorf("get connector oauth state failed: %v", err)
|
||||
ctx.Redirect(http.StatusFound, "/50x")
|
||||
return
|
||||
}
|
||||
if stateInfo != nil && stateInfo.Provider != connector.ConnectorSlugName() {
|
||||
log.Errorf("connector oauth state provider mismatch: %s != %s",
|
||||
stateInfo.Provider, connector.ConnectorSlugName())
|
||||
ctx.Redirect(http.StatusFound, "/50x")
|
||||
return
|
||||
}
|
||||
if stateInfo != nil && stateInfo.Intent == schema.ExternalLoginOAuthStateBindIntent {
|
||||
if err = cc.userExternalService.BindExternalLoginToUser(ctx, stateInfo.UserID, u); err != nil {
|
||||
log.Errorf("bind external login failed: %v", err)
|
||||
ctx.Redirect(http.StatusFound, "/50x")
|
||||
return
|
||||
}
|
||||
ctx.Redirect(http.StatusFound, fmt.Sprintf("%s/users/settings/account", siteGeneral.SiteUrl))
|
||||
return
|
||||
}
|
||||
resp, err := cc.userExternalService.ExternalLogin(ctx, u)
|
||||
if err != nil {
|
||||
log.Errorf("external login failed: %v", err)
|
||||
@@ -237,19 +280,32 @@ func (cc *ConnectorController) ConnectorsUserInfo(ctx *gin.Context) {
|
||||
}
|
||||
|
||||
resp := make([]*schema.ConnectorUserInfoResp, 0)
|
||||
_ = plugin.CallConnector(func(fn plugin.Connector) error {
|
||||
err = plugin.CallConnector(func(fn plugin.Connector) error {
|
||||
externalID := userExternalLoginMapping[fn.ConnectorSlugName()]
|
||||
connectorName := fn.ConnectorName()
|
||||
link := fmt.Sprintf("%s%s%s%s", general.SiteUrl,
|
||||
commonRouterPrefix, ConnectorLoginRouterPrefix, fn.ConnectorSlugName())
|
||||
if len(externalID) == 0 {
|
||||
state, err := cc.userExternalService.GenerateOAuthState(ctx, fn.ConnectorSlugName(),
|
||||
schema.ExternalLoginOAuthStateBindIntent, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
link = fmt.Sprintf("%s?state=%s", link, url.QueryEscape(state))
|
||||
}
|
||||
resp = append(resp, &schema.ConnectorUserInfoResp{
|
||||
Name: connectorName.Translate(ctx),
|
||||
Icon: fn.ConnectorLogoSVG(),
|
||||
Link: fmt.Sprintf("%s%s%s%s", general.SiteUrl,
|
||||
commonRouterPrefix, ConnectorLoginRouterPrefix, fn.ConnectorSlugName()),
|
||||
Name: connectorName.Translate(ctx),
|
||||
Icon: fn.ConnectorLogoSVG(),
|
||||
Link: link,
|
||||
Binding: len(externalID) > 0,
|
||||
ExternalID: externalID,
|
||||
})
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
return
|
||||
}
|
||||
handler.HandleResponse(ctx, nil, resp)
|
||||
}
|
||||
|
||||
|
||||
@@ -54,4 +54,7 @@ var ProviderSetController = wire.NewSet(
|
||||
NewBadgeController,
|
||||
NewRenderController,
|
||||
NewSidebarController,
|
||||
NewMCPController,
|
||||
NewAIController,
|
||||
NewAIConversationController,
|
||||
)
|
||||
|
||||
@@ -48,7 +48,7 @@ func NewLangController(tr i18n.Translator, siteInfoService siteinfo_common.SiteI
|
||||
// @Success 200 {object} handler.RespBody{}
|
||||
// @Router /answer/api/v1/language/config [get]
|
||||
func (u *LangController) GetLangMapping(ctx *gin.Context) {
|
||||
data, _ := u.translator.Dump(handler.GetLang(ctx))
|
||||
data, _ := u.translator.Dump(handler.GetLangByCtx(ctx))
|
||||
var resp map[string]any
|
||||
_ = json.Unmarshal(data, &resp)
|
||||
handler.HandleResponse(ctx, nil, resp)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user