Compare commits
	
		
			1 Commits
		
	
	
		
			canary
			...
			53b49845b2
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					53b49845b2 | 
| 
		 Before Width: | Height: | Size: 2.1 MiB After Width: | Height: | Size: 641 KiB  | 
							
								
								
									
										139
									
								
								.github/workflows/docker-build.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -4,20 +4,12 @@ on:
 | 
				
			|||||||
  push:
 | 
					  push:
 | 
				
			||||||
    branches:
 | 
					    branches:
 | 
				
			||||||
      - master
 | 
					      - master
 | 
				
			||||||
      - canary
 | 
					 | 
				
			||||||
  release:
 | 
					  release:
 | 
				
			||||||
    types: [published]
 | 
					    types: [published]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
jobs:
 | 
					jobs:
 | 
				
			||||||
  build-amd64:
 | 
					  build-amd64:
 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    strategy:
 | 
					 | 
				
			||||||
      matrix:
 | 
					 | 
				
			||||||
        variant:
 | 
					 | 
				
			||||||
          - name: full
 | 
					 | 
				
			||||||
            dockerfile: Dockerfile
 | 
					 | 
				
			||||||
          - name: slim
 | 
					 | 
				
			||||||
            dockerfile: Dockerfile.slim
 | 
					 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - name: Checkout code
 | 
					      - name: Checkout code
 | 
				
			||||||
        uses: actions/checkout@v3
 | 
					        uses: actions/checkout@v3
 | 
				
			||||||
@@ -38,54 +30,34 @@ jobs:
 | 
				
			|||||||
        id: version
 | 
					        id: version
 | 
				
			||||||
        run: echo "RELEASE_VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
 | 
					        run: echo "RELEASE_VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Build and push AMD64 Docker image (master)
 | 
					      - name: Build and push AMD64 Docker image
 | 
				
			||||||
        if: github.ref == 'refs/heads/master' && github.event_name == 'push'
 | 
					        if: github.ref == 'refs/heads/master' && github.event_name == 'push'
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          DOCKERFILE=${{ matrix.variant.dockerfile }}
 | 
					          DOCKERFILE=app.dockerfile
 | 
				
			||||||
          VARIANT=${{ matrix.variant.name }}
 | 
					          IMAGE_NAME=perplexica
 | 
				
			||||||
          docker buildx build --platform linux/amd64 \
 | 
					          docker buildx build --platform linux/amd64 \
 | 
				
			||||||
            --cache-from=type=registry,ref=itzcrazykns1337/perplexica:${VARIANT}-amd64 \
 | 
					            --cache-from=type=registry,ref=itzcrazykns1337/${IMAGE_NAME}:amd64 \
 | 
				
			||||||
            --cache-to=type=inline \
 | 
					            --cache-to=type=inline \
 | 
				
			||||||
            --provenance false \
 | 
					            --provenance false \
 | 
				
			||||||
            -f $DOCKERFILE \
 | 
					            -f $DOCKERFILE \
 | 
				
			||||||
            -t itzcrazykns1337/perplexica:${VARIANT}-amd64 \
 | 
					            -t itzcrazykns1337/${IMAGE_NAME}:amd64 \
 | 
				
			||||||
            --push .
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Build and push AMD64 Canary Docker image
 | 
					 | 
				
			||||||
        if: github.ref == 'refs/heads/canary' && github.event_name == 'push'
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          DOCKERFILE=${{ matrix.variant.dockerfile }}
 | 
					 | 
				
			||||||
          VARIANT=${{ matrix.variant.name }}
 | 
					 | 
				
			||||||
          docker buildx build --platform linux/amd64 \
 | 
					 | 
				
			||||||
            --cache-from=type=registry,ref=itzcrazykns1337/perplexica:${VARIANT}-canary-amd64 \
 | 
					 | 
				
			||||||
            --cache-to=type=inline \
 | 
					 | 
				
			||||||
            --provenance false \
 | 
					 | 
				
			||||||
            -f $DOCKERFILE \
 | 
					 | 
				
			||||||
            -t itzcrazykns1337/perplexica:${VARIANT}-canary-amd64 \
 | 
					 | 
				
			||||||
            --push .
 | 
					            --push .
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Build and push AMD64 release Docker image
 | 
					      - name: Build and push AMD64 release Docker image
 | 
				
			||||||
        if: github.event_name == 'release'
 | 
					        if: github.event_name == 'release'
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          DOCKERFILE=${{ matrix.variant.dockerfile }}
 | 
					          DOCKERFILE=app.dockerfile
 | 
				
			||||||
          VARIANT=${{ matrix.variant.name }}
 | 
					          IMAGE_NAME=perplexica
 | 
				
			||||||
          docker buildx build --platform linux/amd64 \
 | 
					          docker buildx build --platform linux/amd64 \
 | 
				
			||||||
            --cache-from=type=registry,ref=itzcrazykns1337/perplexica:${VARIANT}-${{ env.RELEASE_VERSION }}-amd64 \
 | 
					            --cache-from=type=registry,ref=itzcrazykns1337/${IMAGE_NAME}:${{ env.RELEASE_VERSION }}-amd64 \
 | 
				
			||||||
            --cache-to=type=inline \
 | 
					            --cache-to=type=inline \
 | 
				
			||||||
            --provenance false \
 | 
					            --provenance false \
 | 
				
			||||||
            -f $DOCKERFILE \
 | 
					            -f $DOCKERFILE \
 | 
				
			||||||
            -t itzcrazykns1337/perplexica:${VARIANT}-${{ env.RELEASE_VERSION }}-amd64 \
 | 
					            -t itzcrazykns1337/${IMAGE_NAME}:${{ env.RELEASE_VERSION }}-amd64 \
 | 
				
			||||||
            --push .
 | 
					            --push .
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  build-arm64:
 | 
					  build-arm64:
 | 
				
			||||||
    runs-on: ubuntu-24.04-arm
 | 
					    runs-on: ubuntu-24.04-arm
 | 
				
			||||||
    strategy:
 | 
					 | 
				
			||||||
      matrix:
 | 
					 | 
				
			||||||
        variant:
 | 
					 | 
				
			||||||
          - name: full
 | 
					 | 
				
			||||||
            dockerfile: Dockerfile
 | 
					 | 
				
			||||||
          - name: slim
 | 
					 | 
				
			||||||
            dockerfile: Dockerfile.slim
 | 
					 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - name: Checkout code
 | 
					      - name: Checkout code
 | 
				
			||||||
        uses: actions/checkout@v3
 | 
					        uses: actions/checkout@v3
 | 
				
			||||||
@@ -106,51 +78,35 @@ jobs:
 | 
				
			|||||||
        id: version
 | 
					        id: version
 | 
				
			||||||
        run: echo "RELEASE_VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
 | 
					        run: echo "RELEASE_VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Build and push ARM64 Docker image (master)
 | 
					      - name: Build and push ARM64 Docker image
 | 
				
			||||||
        if: github.ref == 'refs/heads/master' && github.event_name == 'push'
 | 
					        if: github.ref == 'refs/heads/master' && github.event_name == 'push'
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          DOCKERFILE=${{ matrix.variant.dockerfile }}
 | 
					          DOCKERFILE=app.dockerfile
 | 
				
			||||||
          VARIANT=${{ matrix.variant.name }}
 | 
					          IMAGE_NAME=perplexica
 | 
				
			||||||
          docker buildx build --platform linux/arm64 \
 | 
					          docker buildx build --platform linux/arm64 \
 | 
				
			||||||
            --cache-from=type=registry,ref=itzcrazykns1337/perplexica:${VARIANT}-arm64 \
 | 
					            --cache-from=type=registry,ref=itzcrazykns1337/${IMAGE_NAME}:arm64 \
 | 
				
			||||||
            --cache-to=type=inline \
 | 
					            --cache-to=type=inline \
 | 
				
			||||||
            --provenance false \
 | 
					            --provenance false \
 | 
				
			||||||
            -f $DOCKERFILE \
 | 
					            -f $DOCKERFILE \
 | 
				
			||||||
            -t itzcrazykns1337/perplexica:${VARIANT}-arm64 \
 | 
					            -t itzcrazykns1337/${IMAGE_NAME}:arm64 \
 | 
				
			||||||
            --push .
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Build and push ARM64 Canary Docker image
 | 
					 | 
				
			||||||
        if: github.ref == 'refs/heads/canary' && github.event_name == 'push'
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          DOCKERFILE=${{ matrix.variant.dockerfile }}
 | 
					 | 
				
			||||||
          VARIANT=${{ matrix.variant.name }}
 | 
					 | 
				
			||||||
          docker buildx build --platform linux/arm64 \
 | 
					 | 
				
			||||||
            --cache-from=type=registry,ref=itzcrazykns1337/perplexica:${VARIANT}-canary-arm64 \
 | 
					 | 
				
			||||||
            --cache-to=type=inline \
 | 
					 | 
				
			||||||
            --provenance false \
 | 
					 | 
				
			||||||
            -f $DOCKERFILE \
 | 
					 | 
				
			||||||
            -t itzcrazykns1337/perplexica:${VARIANT}-canary-arm64 \
 | 
					 | 
				
			||||||
            --push .
 | 
					            --push .
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Build and push ARM64 release Docker image
 | 
					      - name: Build and push ARM64 release Docker image
 | 
				
			||||||
        if: github.event_name == 'release'
 | 
					        if: github.event_name == 'release'
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          DOCKERFILE=${{ matrix.variant.dockerfile }}
 | 
					          DOCKERFILE=app.dockerfile
 | 
				
			||||||
          VARIANT=${{ matrix.variant.name }}
 | 
					          IMAGE_NAME=perplexica
 | 
				
			||||||
          docker buildx build --platform linux/arm64 \
 | 
					          docker buildx build --platform linux/arm64 \
 | 
				
			||||||
            --cache-from=type=registry,ref=itzcrazykns1337/perplexica:${VARIANT}-${{ env.RELEASE_VERSION }}-arm64 \
 | 
					            --cache-from=type=registry,ref=itzcrazykns1337/${IMAGE_NAME}:${{ env.RELEASE_VERSION }}-arm64 \
 | 
				
			||||||
            --cache-to=type=inline \
 | 
					            --cache-to=type=inline \
 | 
				
			||||||
            --provenance false \
 | 
					            --provenance false \
 | 
				
			||||||
            -f $DOCKERFILE \
 | 
					            -f $DOCKERFILE \
 | 
				
			||||||
            -t itzcrazykns1337/perplexica:${VARIANT}-${{ env.RELEASE_VERSION }}-arm64 \
 | 
					            -t itzcrazykns1337/${IMAGE_NAME}:${{ env.RELEASE_VERSION }}-arm64 \
 | 
				
			||||||
            --push .
 | 
					            --push .
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  manifest:
 | 
					  manifest:
 | 
				
			||||||
    needs: [build-amd64, build-arm64]
 | 
					    needs: [build-amd64, build-arm64]
 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    strategy:
 | 
					 | 
				
			||||||
      matrix:
 | 
					 | 
				
			||||||
        variant: [full, slim]
 | 
					 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - name: Log in to DockerHub
 | 
					      - name: Log in to DockerHub
 | 
				
			||||||
        uses: docker/login-action@v2
 | 
					        uses: docker/login-action@v2
 | 
				
			||||||
@@ -163,55 +119,20 @@ jobs:
 | 
				
			|||||||
        id: version
 | 
					        id: version
 | 
				
			||||||
        run: echo "RELEASE_VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
 | 
					        run: echo "RELEASE_VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Create and push manifest for main
 | 
					      - name: Create and push multi-arch manifest for main
 | 
				
			||||||
        if: github.ref == 'refs/heads/master' && github.event_name == 'push'
 | 
					        if: github.ref == 'refs/heads/master' && github.event_name == 'push'
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          VARIANT=${{ matrix.variant }}
 | 
					          IMAGE_NAME=perplexica
 | 
				
			||||||
          docker manifest create itzcrazykns1337/perplexica:${VARIANT}-latest \
 | 
					          docker manifest create itzcrazykns1337/${IMAGE_NAME}:main \
 | 
				
			||||||
            --amend itzcrazykns1337/perplexica:${VARIANT}-amd64 \
 | 
					            --amend itzcrazykns1337/${IMAGE_NAME}:amd64 \
 | 
				
			||||||
            --amend itzcrazykns1337/perplexica:${VARIANT}-arm64
 | 
					            --amend itzcrazykns1337/${IMAGE_NAME}:arm64
 | 
				
			||||||
          docker manifest push itzcrazykns1337/perplexica:${VARIANT}-latest
 | 
					          docker manifest push itzcrazykns1337/${IMAGE_NAME}:main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          if [ "$VARIANT" = "full" ]; then
 | 
					      - name: Create and push multi-arch manifest for releases
 | 
				
			||||||
            docker manifest create itzcrazykns1337/perplexica:latest \
 | 
					 | 
				
			||||||
              --amend itzcrazykns1337/perplexica:${VARIANT}-amd64 \
 | 
					 | 
				
			||||||
              --amend itzcrazykns1337/perplexica:${VARIANT}-arm64
 | 
					 | 
				
			||||||
            docker manifest push itzcrazykns1337/perplexica:latest
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            docker manifest create itzcrazykns1337/perplexica:main \
 | 
					 | 
				
			||||||
              --amend itzcrazykns1337/perplexica:${VARIANT}-amd64 \
 | 
					 | 
				
			||||||
              --amend itzcrazykns1337/perplexica:${VARIANT}-arm64
 | 
					 | 
				
			||||||
            docker manifest push itzcrazykns1337/perplexica:main
 | 
					 | 
				
			||||||
          fi
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Create and push manifest for canary
 | 
					 | 
				
			||||||
        if: github.ref == 'refs/heads/canary' && github.event_name == 'push'
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          VARIANT=${{ matrix.variant }}
 | 
					 | 
				
			||||||
          docker manifest create itzcrazykns1337/perplexica:${VARIANT}-canary \
 | 
					 | 
				
			||||||
            --amend itzcrazykns1337/perplexica:${VARIANT}-canary-amd64 \
 | 
					 | 
				
			||||||
            --amend itzcrazykns1337/perplexica:${VARIANT}-canary-arm64
 | 
					 | 
				
			||||||
          docker manifest push itzcrazykns1337/perplexica:${VARIANT}-canary
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          if [ "$VARIANT" = "full" ]; then
 | 
					 | 
				
			||||||
            docker manifest create itzcrazykns1337/perplexica:canary \
 | 
					 | 
				
			||||||
              --amend itzcrazykns1337/perplexica:${VARIANT}-canary-amd64 \
 | 
					 | 
				
			||||||
              --amend itzcrazykns1337/perplexica:${VARIANT}-canary-arm64
 | 
					 | 
				
			||||||
            docker manifest push itzcrazykns1337/perplexica:canary
 | 
					 | 
				
			||||||
          fi
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Create and push manifest for releases
 | 
					 | 
				
			||||||
        if: github.event_name == 'release'
 | 
					        if: github.event_name == 'release'
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          VARIANT=${{ matrix.variant }}
 | 
					          IMAGE_NAME=perplexica
 | 
				
			||||||
          docker manifest create itzcrazykns1337/perplexica:${VARIANT}-${{ env.RELEASE_VERSION }} \
 | 
					          docker manifest create itzcrazykns1337/${IMAGE_NAME}:${{ env.RELEASE_VERSION }} \
 | 
				
			||||||
            --amend itzcrazykns1337/perplexica:${VARIANT}-${{ env.RELEASE_VERSION }}-amd64 \
 | 
					            --amend itzcrazykns1337/${IMAGE_NAME}:${{ env.RELEASE_VERSION }}-amd64 \
 | 
				
			||||||
            --amend itzcrazykns1337/perplexica:${VARIANT}-${{ env.RELEASE_VERSION }}-arm64
 | 
					            --amend itzcrazykns1337/${IMAGE_NAME}:${{ env.RELEASE_VERSION }}-arm64
 | 
				
			||||||
          docker manifest push itzcrazykns1337/perplexica:${VARIANT}-${{ env.RELEASE_VERSION }}
 | 
					          docker manifest push itzcrazykns1337/${IMAGE_NAME}:${{ env.RELEASE_VERSION }}
 | 
				
			||||||
 | 
					 | 
				
			||||||
          if [ "$VARIANT" = "full" ]; then
 | 
					 | 
				
			||||||
            docker manifest create itzcrazykns1337/perplexica:${{ env.RELEASE_VERSION }} \
 | 
					 | 
				
			||||||
              --amend itzcrazykns1337/perplexica:${VARIANT}-${{ env.RELEASE_VERSION }}-amd64 \
 | 
					 | 
				
			||||||
              --amend itzcrazykns1337/perplexica:${VARIANT}-${{ env.RELEASE_VERSION }}-arm64
 | 
					 | 
				
			||||||
            docker manifest push itzcrazykns1337/perplexica:${{ env.RELEASE_VERSION }}
 | 
					 | 
				
			||||||
          fi
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -37,5 +37,3 @@ Thumbs.db
 | 
				
			|||||||
# Db
 | 
					# Db
 | 
				
			||||||
db.sqlite
 | 
					db.sqlite
 | 
				
			||||||
/searxng
 | 
					/searxng
 | 
				
			||||||
 | 
					 | 
				
			||||||
certificates
 | 
					 | 
				
			||||||
@@ -36,7 +36,7 @@ Before diving into coding, setting up your local environment is key. Here's what
 | 
				
			|||||||
1. In the root directory, locate the `sample.config.toml` file.
 | 
					1. In the root directory, locate the `sample.config.toml` file.
 | 
				
			||||||
2. Rename it to `config.toml` and fill in the necessary configuration fields.
 | 
					2. Rename it to `config.toml` and fill in the necessary configuration fields.
 | 
				
			||||||
3. Run `npm install` to install all dependencies.
 | 
					3. Run `npm install` to install all dependencies.
 | 
				
			||||||
4. Run `npm run db:migrate` to set up the local sqlite database.
 | 
					4. Run `npm run db:push` to set up the local sqlite database.
 | 
				
			||||||
5. Use `npm run dev` to start the application in development mode.
 | 
					5. Use `npm run dev` to start the application in development mode.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**Please note**: Docker configurations are present for setting up production environments, whereas `npm run dev` is used for development purposes.
 | 
					**Please note**: Docker configurations are present for setting up production environments, whereas `npm run dev` is used for development purposes.
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										74
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						@@ -1,74 +0,0 @@
 | 
				
			|||||||
FROM node:24.5.0-slim AS builder
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
RUN apt-get update && apt-get install -y python3 python3-pip sqlite3 && rm -rf /var/lib/apt/lists/*
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
WORKDIR /home/perplexica
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
COPY package.json yarn.lock ./
 | 
					 | 
				
			||||||
RUN yarn install --frozen-lockfile --network-timeout 600000
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
COPY tsconfig.json next.config.mjs next-env.d.ts postcss.config.js drizzle.config.ts tailwind.config.ts ./
 | 
					 | 
				
			||||||
COPY src ./src
 | 
					 | 
				
			||||||
COPY public ./public
 | 
					 | 
				
			||||||
COPY drizzle ./drizzle
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
RUN mkdir -p /home/perplexica/data
 | 
					 | 
				
			||||||
RUN yarn build
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
FROM node:24.5.0-slim
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
RUN apt-get update && apt-get install -y \
 | 
					 | 
				
			||||||
    python3-dev python3-babel python3-venv python-is-python3 \
 | 
					 | 
				
			||||||
    uwsgi uwsgi-plugin-python3 \
 | 
					 | 
				
			||||||
    git build-essential libxslt-dev zlib1g-dev libffi-dev libssl-dev \
 | 
					 | 
				
			||||||
    curl sudo \
 | 
					 | 
				
			||||||
    && rm -rf /var/lib/apt/lists/*
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
WORKDIR /home/perplexica
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
COPY --from=builder /home/perplexica/public ./public
 | 
					 | 
				
			||||||
COPY --from=builder /home/perplexica/.next/static ./public/_next/static
 | 
					 | 
				
			||||||
COPY --from=builder /home/perplexica/.next/standalone ./
 | 
					 | 
				
			||||||
COPY --from=builder /home/perplexica/data ./data
 | 
					 | 
				
			||||||
COPY drizzle ./drizzle
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
RUN mkdir /home/perplexica/uploads
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
RUN useradd --shell /bin/bash --system \
 | 
					 | 
				
			||||||
    --home-dir "/usr/local/searxng" \
 | 
					 | 
				
			||||||
    --comment 'Privacy-respecting metasearch engine' \
 | 
					 | 
				
			||||||
    searxng
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
RUN mkdir "/usr/local/searxng"
 | 
					 | 
				
			||||||
RUN mkdir -p /etc/searxng
 | 
					 | 
				
			||||||
RUN chown -R "searxng:searxng" "/usr/local/searxng"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
COPY searxng/settings.yml /etc/searxng/settings.yml
 | 
					 | 
				
			||||||
COPY searxng/limiter.toml /etc/searxng/limiter.toml
 | 
					 | 
				
			||||||
COPY searxng/uwsgi.ini /etc/searxng/uwsgi.ini
 | 
					 | 
				
			||||||
RUN chown -R searxng:searxng /etc/searxng
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
USER searxng
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
RUN git clone "https://github.com/searxng/searxng" \
 | 
					 | 
				
			||||||
                   "/usr/local/searxng/searxng-src"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
RUN python3 -m venv "/usr/local/searxng/searx-pyenv"
 | 
					 | 
				
			||||||
RUN "/usr/local/searxng/searx-pyenv/bin/pip" install --upgrade pip setuptools wheel pyyaml msgspec
 | 
					 | 
				
			||||||
RUN cd "/usr/local/searxng/searxng-src" && \
 | 
					 | 
				
			||||||
    "/usr/local/searxng/searx-pyenv/bin/pip" install --use-pep517 --no-build-isolation -e .
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
USER root
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
WORKDIR /home/perplexica
 | 
					 | 
				
			||||||
COPY entrypoint.sh ./entrypoint.sh
 | 
					 | 
				
			||||||
RUN chmod +x ./entrypoint.sh
 | 
					 | 
				
			||||||
RUN sed -i 's/\r$//' ./entrypoint.sh || true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
RUN echo "searxng ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
EXPOSE 3000 8080
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
ENV SEARXNG_API_URL=http://localhost:8080
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
CMD ["/home/perplexica/entrypoint.sh"]
 | 
					 | 
				
			||||||
							
								
								
									
										118
									
								
								README.md
									
									
									
									
									
								
							
							
						
						@@ -16,7 +16,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<hr/>
 | 
					<hr/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[](https://discord.gg/26aArMy8tT)
 | 
					[](https://discord.gg/26aArMy8tT)
 | 
				
			||||||
 | 
					
 | 
				
			||||||

 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -29,7 +29,6 @@
 | 
				
			|||||||
  - [Getting Started with Docker (Recommended)](#getting-started-with-docker-recommended)
 | 
					  - [Getting Started with Docker (Recommended)](#getting-started-with-docker-recommended)
 | 
				
			||||||
  - [Non-Docker Installation](#non-docker-installation)
 | 
					  - [Non-Docker Installation](#non-docker-installation)
 | 
				
			||||||
  - [Ollama Connection Errors](#ollama-connection-errors)
 | 
					  - [Ollama Connection Errors](#ollama-connection-errors)
 | 
				
			||||||
  - [Lemonade Connection Errors](#lemonade-connection-errors)
 | 
					 | 
				
			||||||
- [Using as a Search Engine](#using-as-a-search-engine)
 | 
					- [Using as a Search Engine](#using-as-a-search-engine)
 | 
				
			||||||
- [Using Perplexica's API](#using-perplexicas-api)
 | 
					- [Using Perplexica's API](#using-perplexicas-api)
 | 
				
			||||||
- [Expose Perplexica to a network](#expose-perplexica-to-network)
 | 
					- [Expose Perplexica to a network](#expose-perplexica-to-network)
 | 
				
			||||||
@@ -54,7 +53,7 @@ Want to know more about its architecture and how it works? You can read it [here
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
## Features
 | 
					## Features
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- **Local LLMs**: You can utilize local LLMs such as Qwen, DeepSeek, Llama, and Mistral.
 | 
					- **Local LLMs**: You can make use local LLMs such as Llama3 and Mixtral using Ollama.
 | 
				
			||||||
- **Two Main Modes:**
 | 
					- **Two Main Modes:**
 | 
				
			||||||
  - **Copilot Mode:** (In development) Boosts search by generating different queries to find more relevant internet sources. Like normal search instead of just using the context by SearxNG, it visits the top matches and tries to find relevant sources to the user's query directly from the page.
 | 
					  - **Copilot Mode:** (In development) Boosts search by generating different queries to find more relevant internet sources. Like normal search instead of just using the context by SearxNG, it visits the top matches and tries to find relevant sources to the user's query directly from the page.
 | 
				
			||||||
  - **Normal Mode:** Processes your query and performs a web search.
 | 
					  - **Normal Mode:** Processes your query and performs a web search.
 | 
				
			||||||
@@ -76,35 +75,6 @@ There are mainly 2 ways of installing Perplexica - With Docker, Without Docker.
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
### Getting Started with Docker (Recommended)
 | 
					### Getting Started with Docker (Recommended)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Perplexica can be easily run using Docker. Simply run the following command:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
```bash
 | 
					 | 
				
			||||||
docker run -d -p 3000:3000 -v perplexica-data:/home/perplexica/data -v perplexica-uploads:/home/perplexica/uploads --name perplexica itzcrazykns1337/perplexica:latest
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
This will pull and start the Perplexica container with the bundled SearxNG search engine. Once running, open your browser and navigate to http://localhost:3000. You can then configure your settings (API keys, models, etc.) directly in the setup screen.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**Note**: The image includes both Perplexica and SearxNG, so no additional setup is required. The `-v` flags create persistent volumes for your data and uploaded files.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#### Using Perplexica with Your Own SearxNG Instance
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
If you already have SearxNG running, you can use the slim version of Perplexica:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
```bash
 | 
					 | 
				
			||||||
docker run -d -p 3000:3000 -e SEARXNG_API_URL=http://your-searxng-url:8080 -v perplexica-data:/home/perplexica/data -v perplexica-uploads:/home/perplexica/uploads --name perplexica itzcrazykns1337/perplexica:slim-latest
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**Important**: Make sure your SearxNG instance has:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- JSON format enabled in the settings
 | 
					 | 
				
			||||||
- Wolfram Alpha search engine enabled
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Replace `http://your-searxng-url:8080` with your actual SearxNG URL. Then configure your AI provider settings in the setup screen at http://localhost:3000.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#### Advanced Setup (Building from Source)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
If you prefer to build from source or need more control:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
1. Ensure Docker is installed and running on your system.
 | 
					1. Ensure Docker is installed and running on your system.
 | 
				
			||||||
2. Clone the Perplexica repository:
 | 
					2. Clone the Perplexica repository:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -114,62 +84,40 @@ If you prefer to build from source or need more control:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
3. After cloning, navigate to the directory containing the project files.
 | 
					3. After cloning, navigate to the directory containing the project files.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
4. Build and run using Docker:
 | 
					4. Rename the `sample.config.toml` file to `config.toml`. For Docker setups, you need only fill in the following fields:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   - `OPENAI`: Your OpenAI API key. **You only need to fill this if you wish to use OpenAI's models**.
 | 
				
			||||||
 | 
					   - `OLLAMA`: Your Ollama API URL. You should enter it as `http://host.docker.internal:PORT_NUMBER`. If you installed Ollama on port 11434, use `http://host.docker.internal:11434`. For other ports, adjust accordingly. **You need to fill this if you wish to use Ollama's models instead of OpenAI's**.
 | 
				
			||||||
 | 
					   - `GROQ`: Your Groq API key. **You only need to fill this if you wish to use Groq's hosted models**.
 | 
				
			||||||
 | 
					   - `ANTHROPIC`: Your Anthropic API key. **You only need to fill this if you wish to use Anthropic models**.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     **Note**: You can change these after starting Perplexica from the settings dialog.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   - `SIMILARITY_MEASURE`: The similarity measure to use (This is filled by default; you can leave it as is if you are unsure about it.)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					5. Ensure you are in the directory containing the `docker-compose.yaml` file and execute:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
   ```bash
 | 
					   ```bash
 | 
				
			||||||
   docker build -t perplexica .
 | 
					   docker compose up -d
 | 
				
			||||||
   docker run -d -p 3000:3000 -v perplexica-data:/home/perplexica/data -v perplexica-uploads:/home/perplexica/uploads --name perplexica perplexica
 | 
					 | 
				
			||||||
   ```
 | 
					   ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
5. Access Perplexica at http://localhost:3000 and configure your settings in the setup screen.
 | 
					6. Wait a few minutes for the setup to complete. You can access Perplexica at http://localhost:3000 in your web browser.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**Note**: After the containers are built, you can start Perplexica directly from Docker without having to open a terminal.
 | 
					**Note**: After the containers are built, you can start Perplexica directly from Docker without having to open a terminal.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Non-Docker Installation
 | 
					### Non-Docker Installation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
1. Install SearXNG and allow `JSON` format in the SearXNG settings. Make sure Wolfram Alpha search engine is also enabled.
 | 
					1. Install SearXNG and allow `JSON` format in the SearXNG settings.
 | 
				
			||||||
2. Clone the repository:
 | 
					2. Clone the repository and rename the `sample.config.toml` file to `config.toml` in the root directory. Ensure you complete all required fields in this file.
 | 
				
			||||||
 | 
					3. After populating the configuration run `npm i`.
 | 
				
			||||||
   ```bash
 | 
					4. Install the dependencies and then execute `npm run build`.
 | 
				
			||||||
   git clone https://github.com/ItzCrazyKns/Perplexica.git
 | 
					5. Finally, start the app by running `npm rum start`
 | 
				
			||||||
   cd Perplexica
 | 
					 | 
				
			||||||
   ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
3. Install dependencies:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
   ```bash
 | 
					 | 
				
			||||||
   npm i
 | 
					 | 
				
			||||||
   ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
4. Build the application:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
   ```bash
 | 
					 | 
				
			||||||
   npm run build
 | 
					 | 
				
			||||||
   ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
5. Start the application:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
   ```bash
 | 
					 | 
				
			||||||
   npm run start
 | 
					 | 
				
			||||||
   ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
6. Open your browser and navigate to http://localhost:3000 to complete the setup and configure your settings (API keys, models, SearxNG URL, etc.) in the setup screen.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
**Note**: Using Docker is recommended as it simplifies the setup process, especially for managing environment variables and dependencies.
 | 
					**Note**: Using Docker is recommended as it simplifies the setup process, especially for managing environment variables and dependencies.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
See the [installation documentation](https://github.com/ItzCrazyKns/Perplexica/tree/master/docs/installation) for more information like updating, etc.
 | 
					See the [installation documentation](https://github.com/ItzCrazyKns/Perplexica/tree/master/docs/installation) for more information like updating, etc.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Troubleshooting
 | 
					### Ollama Connection Errors
 | 
				
			||||||
 | 
					 | 
				
			||||||
#### Local OpenAI-API-Compliant Servers
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
If Perplexica tells you that you haven't configured any chat model providers, ensure that:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
1. Your server is running on `0.0.0.0` (not `127.0.0.1`) and on the same port you put in the API URL.
 | 
					 | 
				
			||||||
2. You have specified the correct model name loaded by your local LLM server.
 | 
					 | 
				
			||||||
3. You have specified the correct API key, or if one is not defined, you have put _something_ in the API key field and not left it empty.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#### Ollama Connection Errors
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
If you're encountering an Ollama connection error, it is likely due to the backend being unable to connect to Ollama's API. To fix this issue you can:
 | 
					If you're encountering an Ollama connection error, it is likely due to the backend being unable to connect to Ollama's API. To fix this issue you can:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -184,29 +132,10 @@ If you're encountering an Ollama connection error, it is likely due to the backe
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
3. **Linux Users - Expose Ollama to Network:**
 | 
					3. **Linux Users - Expose Ollama to Network:**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
   - Inside `/etc/systemd/system/ollama.service`, you need to add `Environment="OLLAMA_HOST=0.0.0.0:11434"`. (Change the port number if you are using a different one.) Then reload the systemd manager configuration with `systemctl daemon-reload`, and restart Ollama by `systemctl restart ollama`. For more information see [Ollama docs](https://github.com/ollama/ollama/blob/main/docs/faq.md#setting-environment-variables-on-linux)
 | 
					   - Inside `/etc/systemd/system/ollama.service`, you need to add `Environment="OLLAMA_HOST=0.0.0.0"`. Then restart Ollama by `systemctl restart ollama`. For more information see [Ollama docs](https://github.com/ollama/ollama/blob/main/docs/faq.md#setting-environment-variables-on-linux)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
   - Ensure that the port (default is 11434) is not blocked by your firewall.
 | 
					   - Ensure that the port (default is 11434) is not blocked by your firewall.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#### Lemonade Connection Errors
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
If you're encountering a Lemonade connection error, it is likely due to the backend being unable to connect to Lemonade's API. To fix this issue you can:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
1. **Check your Lemonade API URL:** Ensure that the API URL is correctly set in the settings menu.
 | 
					 | 
				
			||||||
2. **Update API URL Based on OS:**
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
   - **Windows:** Use `http://host.docker.internal:8000`
 | 
					 | 
				
			||||||
   - **Mac:** Use `http://host.docker.internal:8000`
 | 
					 | 
				
			||||||
   - **Linux:** Use `http://<private_ip_of_host>:8000`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
   Adjust the port number if you're using a different one.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
3. **Ensure Lemonade Server is Running:**
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
   - Make sure your Lemonade server is running and accessible on the configured port (default is 8000).
 | 
					 | 
				
			||||||
   - Verify that Lemonade is configured to accept connections from all interfaces (`0.0.0.0`), not just localhost (`127.0.0.1`).
 | 
					 | 
				
			||||||
   - Ensure that the port (default is 8000) is not blocked by your firewall.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Using as a Search Engine
 | 
					## Using as a Search Engine
 | 
				
			||||||
 | 
					
 | 
				
			||||||
If you wish to use Perplexica as an alternative to traditional search engines like Google or Bing, or if you want to add a shortcut for quick access from your browser's search bar, follow these steps:
 | 
					If you wish to use Perplexica as an alternative to traditional search engines like Google or Bing, or if you want to add a shortcut for quick access from your browser's search bar, follow these steps:
 | 
				
			||||||
@@ -231,7 +160,6 @@ Perplexica runs on Next.js and handles all API requests. It works right away on
 | 
				
			|||||||
[](https://usw.sealos.io/?openapp=system-template%3FtemplateName%3Dperplexica)
 | 
					[](https://usw.sealos.io/?openapp=system-template%3FtemplateName%3Dperplexica)
 | 
				
			||||||
[](https://repocloud.io/details/?app_id=267)
 | 
					[](https://repocloud.io/details/?app_id=267)
 | 
				
			||||||
[](https://template.run.claw.cloud/?referralCode=U11MRQ8U9RM4&openapp=system-fastdeploy%3FtemplateName%3Dperplexica)
 | 
					[](https://template.run.claw.cloud/?referralCode=U11MRQ8U9RM4&openapp=system-fastdeploy%3FtemplateName%3Dperplexica)
 | 
				
			||||||
[](https://www.hostinger.com/vps/docker-hosting?compose_url=https://raw.githubusercontent.com/ItzCrazyKns/Perplexica/refs/heads/master/docker-compose.yaml)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Upcoming Features
 | 
					## Upcoming Features
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,4 @@
 | 
				
			|||||||
FROM node:24.5.0-slim AS builder
 | 
					FROM node:20.18.0-slim AS builder
 | 
				
			||||||
 | 
					 | 
				
			||||||
RUN apt-get update && apt-get install -y python3 python3-pip sqlite3 && rm -rf /var/lib/apt/lists/*
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
WORKDIR /home/perplexica
 | 
					WORKDIR /home/perplexica
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -10,14 +8,11 @@ RUN yarn install --frozen-lockfile --network-timeout 600000
 | 
				
			|||||||
COPY tsconfig.json next.config.mjs next-env.d.ts postcss.config.js drizzle.config.ts tailwind.config.ts ./
 | 
					COPY tsconfig.json next.config.mjs next-env.d.ts postcss.config.js drizzle.config.ts tailwind.config.ts ./
 | 
				
			||||||
COPY src ./src
 | 
					COPY src ./src
 | 
				
			||||||
COPY public ./public
 | 
					COPY public ./public
 | 
				
			||||||
COPY drizzle ./drizzle
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
RUN mkdir -p /home/perplexica/data
 | 
					RUN mkdir -p /home/perplexica/data
 | 
				
			||||||
RUN yarn build
 | 
					RUN yarn build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
FROM node:24.5.0-slim
 | 
					FROM node:20.18.0-slim
 | 
				
			||||||
 | 
					 | 
				
			||||||
RUN apt-get update && apt-get install -y python3 python3-pip sqlite3 && rm -rf /var/lib/apt/lists/*
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
WORKDIR /home/perplexica
 | 
					WORKDIR /home/perplexica
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -26,10 +21,7 @@ COPY --from=builder /home/perplexica/.next/static ./public/_next/static
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
COPY --from=builder /home/perplexica/.next/standalone ./
 | 
					COPY --from=builder /home/perplexica/.next/standalone ./
 | 
				
			||||||
COPY --from=builder /home/perplexica/data ./data
 | 
					COPY --from=builder /home/perplexica/data ./data
 | 
				
			||||||
COPY drizzle ./drizzle
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
RUN mkdir /home/perplexica/uploads
 | 
					RUN mkdir /home/perplexica/uploads
 | 
				
			||||||
 | 
					
 | 
				
			||||||
EXPOSE 3000
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
CMD ["node", "server.js"]
 | 
					CMD ["node", "server.js"]
 | 
				
			||||||
@@ -1,15 +1,34 @@
 | 
				
			|||||||
services:
 | 
					services:
 | 
				
			||||||
  perplexica:
 | 
					  searxng:
 | 
				
			||||||
    image: itzcrazykns1337/perplexica:latest
 | 
					    image: docker.io/searxng/searxng:latest
 | 
				
			||||||
    ports:
 | 
					 | 
				
			||||||
      - '3000:3000'
 | 
					 | 
				
			||||||
    volumes:
 | 
					    volumes:
 | 
				
			||||||
      - data:/home/perplexica/data
 | 
					      - ./searxng:/etc/searxng:rw
 | 
				
			||||||
      - uploads:/home/perplexica/uploads
 | 
					    ports:
 | 
				
			||||||
 | 
					      - 4000:8080
 | 
				
			||||||
 | 
					    networks:
 | 
				
			||||||
 | 
					      - perplexica-network
 | 
				
			||||||
    restart: unless-stopped
 | 
					    restart: unless-stopped
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  app:
 | 
				
			||||||
 | 
					    image: itzcrazykns1337/perplexica:main
 | 
				
			||||||
 | 
					    build:
 | 
				
			||||||
 | 
					      context: .
 | 
				
			||||||
 | 
					      dockerfile: app.dockerfile
 | 
				
			||||||
 | 
					    environment:
 | 
				
			||||||
 | 
					      - SEARXNG_API_URL=http://searxng:8080
 | 
				
			||||||
 | 
					    ports:
 | 
				
			||||||
 | 
					      - 3000:3000
 | 
				
			||||||
 | 
					    networks:
 | 
				
			||||||
 | 
					      - perplexica-network
 | 
				
			||||||
 | 
					    volumes:
 | 
				
			||||||
 | 
					      - backend-dbstore:/home/perplexica/data
 | 
				
			||||||
 | 
					      - uploads:/home/perplexica/uploads
 | 
				
			||||||
 | 
					      - ./config.toml:/home/perplexica/config.toml
 | 
				
			||||||
 | 
					    restart: unless-stopped
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					networks:
 | 
				
			||||||
 | 
					  perplexica-network:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
volumes:
 | 
					volumes:
 | 
				
			||||||
  data:
 | 
					  backend-dbstore:
 | 
				
			||||||
    name: 'perplexica-data'
 | 
					 | 
				
			||||||
  uploads:
 | 
					  uploads:
 | 
				
			||||||
    name: 'perplexica-uploads'
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,56 +4,11 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
Perplexica’s Search API makes it easy to use our AI-powered search engine. You can run different types of searches, pick the models you want to use, and get the most recent info. Follow the following headings to learn more about Perplexica's search API.
 | 
					Perplexica’s Search API makes it easy to use our AI-powered search engine. You can run different types of searches, pick the models you want to use, and get the most recent info. Follow the following headings to learn more about Perplexica's search API.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Endpoints
 | 
					## Endpoint
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Get Available Providers and Models
 | 
					### **POST** `http://localhost:3000/api/search`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Before making search requests, you'll need to get the available providers and their models.
 | 
					**Note**: Replace `3000` with any other port if you've changed the default PORT
 | 
				
			||||||
 | 
					 | 
				
			||||||
#### **GET** `/api/providers`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**Full URL**: `http://localhost:3000/api/providers`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Returns a list of all active providers with their available chat and embedding models.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**Response Example:**
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
```json
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
  "providers": [
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      "id": "550e8400-e29b-41d4-a716-446655440000",
 | 
					 | 
				
			||||||
      "name": "OpenAI",
 | 
					 | 
				
			||||||
      "chatModels": [
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          "name": "GPT 4 Omni Mini",
 | 
					 | 
				
			||||||
          "key": "gpt-4o-mini"
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          "name": "GPT 4 Omni",
 | 
					 | 
				
			||||||
          "key": "gpt-4o"
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      ],
 | 
					 | 
				
			||||||
      "embeddingModels": [
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          "name": "Text Embedding 3 Large",
 | 
					 | 
				
			||||||
          "key": "text-embedding-3-large"
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      ]
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  ]
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Use the `id` field as the `providerId` and the `key` field from the models arrays when making search requests.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### Search Query
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#### **POST** `/api/search`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**Full URL**: `http://localhost:3000/api/search`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**Note**: Replace `localhost:3000` with your Perplexica instance URL if running on a different host or port
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Request
 | 
					### Request
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -64,12 +19,12 @@ The API accepts a JSON object in the request body, where you define the focus mo
 | 
				
			|||||||
```json
 | 
					```json
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
  "chatModel": {
 | 
					  "chatModel": {
 | 
				
			||||||
    "providerId": "550e8400-e29b-41d4-a716-446655440000",
 | 
					    "provider": "openai",
 | 
				
			||||||
    "key": "gpt-4o-mini"
 | 
					    "name": "gpt-4o-mini"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "embeddingModel": {
 | 
					  "embeddingModel": {
 | 
				
			||||||
    "providerId": "550e8400-e29b-41d4-a716-446655440000",
 | 
					    "provider": "openai",
 | 
				
			||||||
    "key": "text-embedding-3-large"
 | 
					    "name": "text-embedding-3-large"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "optimizationMode": "speed",
 | 
					  "optimizationMode": "speed",
 | 
				
			||||||
  "focusMode": "webSearch",
 | 
					  "focusMode": "webSearch",
 | 
				
			||||||
@@ -83,19 +38,20 @@ The API accepts a JSON object in the request body, where you define the focus mo
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**Note**: The `providerId` must be a valid UUID obtained from the `/api/providers` endpoint. The example above uses a sample UUID for demonstration.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### Request Parameters
 | 
					### Request Parameters
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- **`chatModel`** (object, optional): Defines the chat model to be used for the query. To get available providers and models, send a GET request to `http://localhost:3000/api/providers`.
 | 
					- **`chatModel`** (object, optional): Defines the chat model to be used for the query. For model details you can send a GET request at `http://localhost:3000/api/models`. Make sure to use the key value (For example "gpt-4o-mini" instead of the display name "GPT 4 omni mini").
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - `providerId` (string): The UUID of the provider. You can get this from the `/api/providers` endpoint response.
 | 
					  - `provider`: Specifies the provider for the chat model (e.g., `openai`, `ollama`).
 | 
				
			||||||
  - `key` (string): The model key/identifier (e.g., `gpt-4o-mini`, `llama3.1:latest`). Use the `key` value from the provider's `chatModels` array, not the display name.
 | 
					  - `name`: The specific model from the chosen provider (e.g., `gpt-4o-mini`).
 | 
				
			||||||
 | 
					  - Optional fields for custom OpenAI configuration:
 | 
				
			||||||
 | 
					    - `customOpenAIBaseURL`: If you’re using a custom OpenAI instance, provide the base URL.
 | 
				
			||||||
 | 
					    - `customOpenAIKey`: The API key for a custom OpenAI instance.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- **`embeddingModel`** (object, optional): Defines the embedding model for similarity-based searching. To get available providers and models, send a GET request to `http://localhost:3000/api/providers`.
 | 
					- **`embeddingModel`** (object, optional): Defines the embedding model for similarity-based searching. For model details you can send a GET request at `http://localhost:3000/api/models`. Make sure to use the key value (For example "text-embedding-3-large" instead of the display name "Text Embedding 3 Large").
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - `providerId` (string): The UUID of the embedding provider. You can get this from the `/api/providers` endpoint response.
 | 
					  - `provider`: The provider for the embedding model (e.g., `openai`).
 | 
				
			||||||
  - `key` (string): The embedding model key (e.g., `text-embedding-3-large`, `nomic-embed-text`). Use the `key` value from the provider's `embeddingModels` array, not the display name.
 | 
					  - `name`: The specific embedding model (e.g., `text-embedding-3-large`).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- **`focusMode`** (string, required): Specifies which focus mode to use. Available modes:
 | 
					- **`focusMode`** (string, required): Specifies which focus mode to use. Available modes:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -152,7 +108,7 @@ The response from the API includes both the final message and the sources used t
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#### Streaming Response (stream: true)
 | 
					#### Streaming Response (stream: true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
When streaming is enabled, the API returns a stream of newline-delimited JSON objects using Server-Sent Events (SSE). Each line contains a complete, valid JSON object. The response has `Content-Type: text/event-stream`.
 | 
					When streaming is enabled, the API returns a stream of newline-delimited JSON objects. Each line contains a complete, valid JSON object. The response has Content-Type: application/json.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Example of streamed response objects:
 | 
					Example of streamed response objects:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,80 +2,45 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
To update Perplexica to the latest version, follow these steps:
 | 
					To update Perplexica to the latest version, follow these steps:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## For Docker users (Using pre-built images)
 | 
					## For Docker users
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Simply pull the latest image and restart your container:
 | 
					1. Clone the latest version of Perplexica from GitHub:
 | 
				
			||||||
 | 
					 | 
				
			||||||
```bash
 | 
					 | 
				
			||||||
docker pull itzcrazykns1337/perplexica:latest
 | 
					 | 
				
			||||||
docker stop perplexica
 | 
					 | 
				
			||||||
docker rm perplexica
 | 
					 | 
				
			||||||
docker run -d -p 3000:3000 -v perplexica-data:/home/perplexica/data -v perplexica-uploads:/home/perplexica/uploads --name perplexica itzcrazykns1337/perplexica:latest
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
For slim version:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
```bash
 | 
					 | 
				
			||||||
docker pull itzcrazykns1337/perplexica:slim-latest
 | 
					 | 
				
			||||||
docker stop perplexica
 | 
					 | 
				
			||||||
docker rm perplexica
 | 
					 | 
				
			||||||
docker run -d -p 3000:3000 -e SEARXNG_API_URL=http://your-searxng-url:8080 -v perplexica-data:/home/perplexica/data -v perplexica-uploads:/home/perplexica/uploads --name perplexica itzcrazykns1337/perplexica:slim-latest
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Once updated, go to http://localhost:3000 and verify the latest changes. Your settings are preserved automatically.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## For Docker users (Building from source)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
1. Navigate to your Perplexica directory and pull the latest changes:
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
   ```bash
 | 
					   ```bash
 | 
				
			||||||
   cd Perplexica
 | 
					   git clone https://github.com/ItzCrazyKns/Perplexica.git
 | 
				
			||||||
   git pull origin master
 | 
					 | 
				
			||||||
   ```
 | 
					   ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
2. Rebuild the Docker image:
 | 
					2. Navigate to the project directory.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					3. Check for changes in the configuration files. If the `sample.config.toml` file contains new fields, delete your existing `config.toml` file, rename `sample.config.toml` to `config.toml`, and update the configuration accordingly.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					4. Pull the latest images from the registry.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
   ```bash
 | 
					   ```bash
 | 
				
			||||||
   docker build -t perplexica .
 | 
					   docker compose pull
 | 
				
			||||||
   ```
 | 
					   ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
3. Stop and remove the old container, then start the new one:
 | 
					5. Update and recreate the containers.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
   ```bash
 | 
					   ```bash
 | 
				
			||||||
   docker stop perplexica
 | 
					   docker compose up -d
 | 
				
			||||||
   docker rm perplexica
 | 
					 | 
				
			||||||
   docker run -p 3000:3000 -p 8080:8080 --name perplexica perplexica
 | 
					 | 
				
			||||||
   ```
 | 
					   ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
4. Once the command completes, go to http://localhost:3000 and verify the latest changes.
 | 
					6. Once the command completes, go to http://localhost:3000 and verify the latest changes.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## For non-Docker users
 | 
					## For non-Docker users
 | 
				
			||||||
 | 
					
 | 
				
			||||||
1. Navigate to your Perplexica directory and pull the latest changes:
 | 
					1. Clone the latest version of Perplexica from GitHub:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
   ```bash
 | 
					   ```bash
 | 
				
			||||||
   cd Perplexica
 | 
					   git clone https://github.com/ItzCrazyKns/Perplexica.git
 | 
				
			||||||
   git pull origin master
 | 
					 | 
				
			||||||
   ```
 | 
					   ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
2. Install any new dependencies:
 | 
					2. Navigate to the project directory.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
   ```bash
 | 
					3. Check for changes in the configuration files. If the `sample.config.toml` file contains new fields, delete your existing `config.toml` file, rename `sample.config.toml` to `config.toml`, and update the configuration accordingly.
 | 
				
			||||||
   npm i
 | 
					4. After populating the configuration run `npm i`.
 | 
				
			||||||
   ```
 | 
					5. Install the dependencies and then execute `npm run build`.
 | 
				
			||||||
 | 
					6. Finally, start the app by running `npm rum start`
 | 
				
			||||||
3. Rebuild the application:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
   ```bash
 | 
					 | 
				
			||||||
   npm run build
 | 
					 | 
				
			||||||
   ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
4. Restart the application:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
   ```bash
 | 
					 | 
				
			||||||
   npm run start
 | 
					 | 
				
			||||||
   ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
5. Go to http://localhost:3000 and verify the latest changes. Your settings are preserved automatically.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
---
 | 
					---
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,10 @@
 | 
				
			|||||||
import { defineConfig } from 'drizzle-kit';
 | 
					import { defineConfig } from 'drizzle-kit';
 | 
				
			||||||
import path from 'path';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default defineConfig({
 | 
					export default defineConfig({
 | 
				
			||||||
  dialect: 'sqlite',
 | 
					  dialect: 'sqlite',
 | 
				
			||||||
  schema: './src/lib/db/schema.ts',
 | 
					  schema: './src/lib/db/schema.ts',
 | 
				
			||||||
  out: './drizzle',
 | 
					  out: './drizzle',
 | 
				
			||||||
  dbCredentials: {
 | 
					  dbCredentials: {
 | 
				
			||||||
    url: path.join(process.cwd(), 'data', 'db.sqlite'),
 | 
					    url: './data/db.sqlite',
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,16 +0,0 @@
 | 
				
			|||||||
CREATE TABLE IF NOT EXISTS `chats` (
 | 
					 | 
				
			||||||
	`id` text PRIMARY KEY NOT NULL,
 | 
					 | 
				
			||||||
	`title` text NOT NULL,
 | 
					 | 
				
			||||||
	`createdAt` text NOT NULL,
 | 
					 | 
				
			||||||
	`focusMode` text NOT NULL,
 | 
					 | 
				
			||||||
	`files` text DEFAULT '[]'
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
--> statement-breakpoint
 | 
					 | 
				
			||||||
CREATE TABLE IF NOT EXISTS `messages` (
 | 
					 | 
				
			||||||
	`id` integer PRIMARY KEY NOT NULL,
 | 
					 | 
				
			||||||
	`content` text NOT NULL,
 | 
					 | 
				
			||||||
	`chatId` text NOT NULL,
 | 
					 | 
				
			||||||
	`messageId` text NOT NULL,
 | 
					 | 
				
			||||||
	`type` text,
 | 
					 | 
				
			||||||
	`metadata` text
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
@@ -1 +0,0 @@
 | 
				
			|||||||
/* Do nothing */
 | 
					 | 
				
			||||||
@@ -1,116 +0,0 @@
 | 
				
			|||||||
{
 | 
					 | 
				
			||||||
  "version": "6",
 | 
					 | 
				
			||||||
  "dialect": "sqlite",
 | 
					 | 
				
			||||||
  "id": "ef3a044b-0f34-40b5-babb-2bb3a909ba27",
 | 
					 | 
				
			||||||
  "prevId": "00000000-0000-0000-0000-000000000000",
 | 
					 | 
				
			||||||
  "tables": {
 | 
					 | 
				
			||||||
    "chats": {
 | 
					 | 
				
			||||||
      "name": "chats",
 | 
					 | 
				
			||||||
      "columns": {
 | 
					 | 
				
			||||||
        "id": {
 | 
					 | 
				
			||||||
          "name": "id",
 | 
					 | 
				
			||||||
          "type": "text",
 | 
					 | 
				
			||||||
          "primaryKey": true,
 | 
					 | 
				
			||||||
          "notNull": true,
 | 
					 | 
				
			||||||
          "autoincrement": false
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "title": {
 | 
					 | 
				
			||||||
          "name": "title",
 | 
					 | 
				
			||||||
          "type": "text",
 | 
					 | 
				
			||||||
          "primaryKey": false,
 | 
					 | 
				
			||||||
          "notNull": true,
 | 
					 | 
				
			||||||
          "autoincrement": false
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "createdAt": {
 | 
					 | 
				
			||||||
          "name": "createdAt",
 | 
					 | 
				
			||||||
          "type": "text",
 | 
					 | 
				
			||||||
          "primaryKey": false,
 | 
					 | 
				
			||||||
          "notNull": true,
 | 
					 | 
				
			||||||
          "autoincrement": false
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "focusMode": {
 | 
					 | 
				
			||||||
          "name": "focusMode",
 | 
					 | 
				
			||||||
          "type": "text",
 | 
					 | 
				
			||||||
          "primaryKey": false,
 | 
					 | 
				
			||||||
          "notNull": true,
 | 
					 | 
				
			||||||
          "autoincrement": false
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "files": {
 | 
					 | 
				
			||||||
          "name": "files",
 | 
					 | 
				
			||||||
          "type": "text",
 | 
					 | 
				
			||||||
          "primaryKey": false,
 | 
					 | 
				
			||||||
          "notNull": false,
 | 
					 | 
				
			||||||
          "autoincrement": false,
 | 
					 | 
				
			||||||
          "default": "'[]'"
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "indexes": {},
 | 
					 | 
				
			||||||
      "foreignKeys": {},
 | 
					 | 
				
			||||||
      "compositePrimaryKeys": {},
 | 
					 | 
				
			||||||
      "uniqueConstraints": {},
 | 
					 | 
				
			||||||
      "checkConstraints": {}
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "messages": {
 | 
					 | 
				
			||||||
      "name": "messages",
 | 
					 | 
				
			||||||
      "columns": {
 | 
					 | 
				
			||||||
        "id": {
 | 
					 | 
				
			||||||
          "name": "id",
 | 
					 | 
				
			||||||
          "type": "integer",
 | 
					 | 
				
			||||||
          "primaryKey": true,
 | 
					 | 
				
			||||||
          "notNull": true,
 | 
					 | 
				
			||||||
          "autoincrement": false
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "content": {
 | 
					 | 
				
			||||||
          "name": "content",
 | 
					 | 
				
			||||||
          "type": "text",
 | 
					 | 
				
			||||||
          "primaryKey": false,
 | 
					 | 
				
			||||||
          "notNull": true,
 | 
					 | 
				
			||||||
          "autoincrement": false
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "chatId": {
 | 
					 | 
				
			||||||
          "name": "chatId",
 | 
					 | 
				
			||||||
          "type": "text",
 | 
					 | 
				
			||||||
          "primaryKey": false,
 | 
					 | 
				
			||||||
          "notNull": true,
 | 
					 | 
				
			||||||
          "autoincrement": false
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "messageId": {
 | 
					 | 
				
			||||||
          "name": "messageId",
 | 
					 | 
				
			||||||
          "type": "text",
 | 
					 | 
				
			||||||
          "primaryKey": false,
 | 
					 | 
				
			||||||
          "notNull": true,
 | 
					 | 
				
			||||||
          "autoincrement": false
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "type": {
 | 
					 | 
				
			||||||
          "name": "type",
 | 
					 | 
				
			||||||
          "type": "text",
 | 
					 | 
				
			||||||
          "primaryKey": false,
 | 
					 | 
				
			||||||
          "notNull": false,
 | 
					 | 
				
			||||||
          "autoincrement": false
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "metadata": {
 | 
					 | 
				
			||||||
          "name": "metadata",
 | 
					 | 
				
			||||||
          "type": "text",
 | 
					 | 
				
			||||||
          "primaryKey": false,
 | 
					 | 
				
			||||||
          "notNull": false,
 | 
					 | 
				
			||||||
          "autoincrement": false
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "indexes": {},
 | 
					 | 
				
			||||||
      "foreignKeys": {},
 | 
					 | 
				
			||||||
      "compositePrimaryKeys": {},
 | 
					 | 
				
			||||||
      "uniqueConstraints": {},
 | 
					 | 
				
			||||||
      "checkConstraints": {}
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "views": {},
 | 
					 | 
				
			||||||
  "enums": {},
 | 
					 | 
				
			||||||
  "_meta": {
 | 
					 | 
				
			||||||
    "schemas": {},
 | 
					 | 
				
			||||||
    "tables": {},
 | 
					 | 
				
			||||||
    "columns": {}
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "internal": {
 | 
					 | 
				
			||||||
    "indexes": {}
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,125 +0,0 @@
 | 
				
			|||||||
{
 | 
					 | 
				
			||||||
  "version": "6",
 | 
					 | 
				
			||||||
  "dialect": "sqlite",
 | 
					 | 
				
			||||||
  "id": "6dedf55f-0e44-478f-82cf-14a21ac686f8",
 | 
					 | 
				
			||||||
  "prevId": "ef3a044b-0f34-40b5-babb-2bb3a909ba27",
 | 
					 | 
				
			||||||
  "tables": {
 | 
					 | 
				
			||||||
    "chats": {
 | 
					 | 
				
			||||||
      "name": "chats",
 | 
					 | 
				
			||||||
      "columns": {
 | 
					 | 
				
			||||||
        "id": {
 | 
					 | 
				
			||||||
          "name": "id",
 | 
					 | 
				
			||||||
          "type": "text",
 | 
					 | 
				
			||||||
          "primaryKey": true,
 | 
					 | 
				
			||||||
          "notNull": true,
 | 
					 | 
				
			||||||
          "autoincrement": false
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "title": {
 | 
					 | 
				
			||||||
          "name": "title",
 | 
					 | 
				
			||||||
          "type": "text",
 | 
					 | 
				
			||||||
          "primaryKey": false,
 | 
					 | 
				
			||||||
          "notNull": true,
 | 
					 | 
				
			||||||
          "autoincrement": false
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "createdAt": {
 | 
					 | 
				
			||||||
          "name": "createdAt",
 | 
					 | 
				
			||||||
          "type": "text",
 | 
					 | 
				
			||||||
          "primaryKey": false,
 | 
					 | 
				
			||||||
          "notNull": true,
 | 
					 | 
				
			||||||
          "autoincrement": false
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "focusMode": {
 | 
					 | 
				
			||||||
          "name": "focusMode",
 | 
					 | 
				
			||||||
          "type": "text",
 | 
					 | 
				
			||||||
          "primaryKey": false,
 | 
					 | 
				
			||||||
          "notNull": true,
 | 
					 | 
				
			||||||
          "autoincrement": false
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "files": {
 | 
					 | 
				
			||||||
          "name": "files",
 | 
					 | 
				
			||||||
          "type": "text",
 | 
					 | 
				
			||||||
          "primaryKey": false,
 | 
					 | 
				
			||||||
          "notNull": false,
 | 
					 | 
				
			||||||
          "autoincrement": false,
 | 
					 | 
				
			||||||
          "default": "'[]'"
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "indexes": {},
 | 
					 | 
				
			||||||
      "foreignKeys": {},
 | 
					 | 
				
			||||||
      "compositePrimaryKeys": {},
 | 
					 | 
				
			||||||
      "uniqueConstraints": {},
 | 
					 | 
				
			||||||
      "checkConstraints": {}
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "messages": {
 | 
					 | 
				
			||||||
      "name": "messages",
 | 
					 | 
				
			||||||
      "columns": {
 | 
					 | 
				
			||||||
        "id": {
 | 
					 | 
				
			||||||
          "name": "id",
 | 
					 | 
				
			||||||
          "type": "integer",
 | 
					 | 
				
			||||||
          "primaryKey": true,
 | 
					 | 
				
			||||||
          "notNull": true,
 | 
					 | 
				
			||||||
          "autoincrement": false
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "type": {
 | 
					 | 
				
			||||||
          "name": "type",
 | 
					 | 
				
			||||||
          "type": "text",
 | 
					 | 
				
			||||||
          "primaryKey": false,
 | 
					 | 
				
			||||||
          "notNull": true,
 | 
					 | 
				
			||||||
          "autoincrement": false
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "chatId": {
 | 
					 | 
				
			||||||
          "name": "chatId",
 | 
					 | 
				
			||||||
          "type": "text",
 | 
					 | 
				
			||||||
          "primaryKey": false,
 | 
					 | 
				
			||||||
          "notNull": true,
 | 
					 | 
				
			||||||
          "autoincrement": false
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "createdAt": {
 | 
					 | 
				
			||||||
          "name": "createdAt",
 | 
					 | 
				
			||||||
          "type": "text",
 | 
					 | 
				
			||||||
          "primaryKey": false,
 | 
					 | 
				
			||||||
          "notNull": true,
 | 
					 | 
				
			||||||
          "autoincrement": false,
 | 
					 | 
				
			||||||
          "default": "CURRENT_TIMESTAMP"
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "messageId": {
 | 
					 | 
				
			||||||
          "name": "messageId",
 | 
					 | 
				
			||||||
          "type": "text",
 | 
					 | 
				
			||||||
          "primaryKey": false,
 | 
					 | 
				
			||||||
          "notNull": true,
 | 
					 | 
				
			||||||
          "autoincrement": false
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "content": {
 | 
					 | 
				
			||||||
          "name": "content",
 | 
					 | 
				
			||||||
          "type": "text",
 | 
					 | 
				
			||||||
          "primaryKey": false,
 | 
					 | 
				
			||||||
          "notNull": false,
 | 
					 | 
				
			||||||
          "autoincrement": false
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "sources": {
 | 
					 | 
				
			||||||
          "name": "sources",
 | 
					 | 
				
			||||||
          "type": "text",
 | 
					 | 
				
			||||||
          "primaryKey": false,
 | 
					 | 
				
			||||||
          "notNull": false,
 | 
					 | 
				
			||||||
          "autoincrement": false,
 | 
					 | 
				
			||||||
          "default": "'[]'"
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      "indexes": {},
 | 
					 | 
				
			||||||
      "foreignKeys": {},
 | 
					 | 
				
			||||||
      "compositePrimaryKeys": {},
 | 
					 | 
				
			||||||
      "uniqueConstraints": {},
 | 
					 | 
				
			||||||
      "checkConstraints": {}
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "views": {},
 | 
					 | 
				
			||||||
  "enums": {},
 | 
					 | 
				
			||||||
  "_meta": {
 | 
					 | 
				
			||||||
    "schemas": {},
 | 
					 | 
				
			||||||
    "tables": {},
 | 
					 | 
				
			||||||
    "columns": {}
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "internal": {
 | 
					 | 
				
			||||||
    "indexes": {}
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,20 +0,0 @@
 | 
				
			|||||||
{
 | 
					 | 
				
			||||||
  "version": "7",
 | 
					 | 
				
			||||||
  "dialect": "sqlite",
 | 
					 | 
				
			||||||
  "entries": [
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      "idx": 0,
 | 
					 | 
				
			||||||
      "version": "6",
 | 
					 | 
				
			||||||
      "when": 1748405503809,
 | 
					 | 
				
			||||||
      "tag": "0000_fuzzy_randall",
 | 
					 | 
				
			||||||
      "breakpoints": true
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      "idx": 1,
 | 
					 | 
				
			||||||
      "version": "6",
 | 
					 | 
				
			||||||
      "when": 1758863991284,
 | 
					 | 
				
			||||||
      "tag": "0001_wise_rockslide",
 | 
					 | 
				
			||||||
      "breakpoints": true
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  ]
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,32 +0,0 @@
 | 
				
			|||||||
#!/bin/sh
 | 
					 | 
				
			||||||
set -e
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
echo "Starting SearXNG..."
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
sudo -H -u searxng bash -c "cd /usr/local/searxng/searxng-src && export SEARXNG_SETTINGS_PATH='/etc/searxng/settings.yml' && export FLASK_APP=searx/webapp.py && /usr/local/searxng/searx-pyenv/bin/python -m flask run --host=0.0.0.0 --port=8080" &
 | 
					 | 
				
			||||||
SEARXNG_PID=$!
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
echo "Waiting for SearXNG to be ready..."
 | 
					 | 
				
			||||||
sleep 5
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
COUNTER=0
 | 
					 | 
				
			||||||
MAX_TRIES=30
 | 
					 | 
				
			||||||
until curl -s http://localhost:8080 > /dev/null 2>&1; do
 | 
					 | 
				
			||||||
  COUNTER=$((COUNTER+1))
 | 
					 | 
				
			||||||
  if [ $COUNTER -ge $MAX_TRIES ]; then
 | 
					 | 
				
			||||||
    echo "Warning: SearXNG health check timeout, but continuing..."
 | 
					 | 
				
			||||||
    break
 | 
					 | 
				
			||||||
  fi
 | 
					 | 
				
			||||||
  sleep 1
 | 
					 | 
				
			||||||
done
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if curl -s http://localhost:8080 > /dev/null 2>&1; then
 | 
					 | 
				
			||||||
  echo "SearXNG started successfully (PID: $SEARXNG_PID)"
 | 
					 | 
				
			||||||
else
 | 
					 | 
				
			||||||
  echo "SearXNG may not be fully ready, but continuing (PID: $SEARXNG_PID)"
 | 
					 | 
				
			||||||
fi
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
cd /home/perplexica
 | 
					 | 
				
			||||||
echo "Starting Perplexica..."
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
exec node server.js
 | 
					 | 
				
			||||||
							
								
								
									
										35
									
								
								package.json
									
									
									
									
									
								
							
							
						
						@@ -1,41 +1,37 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "name": "perplexica-frontend",
 | 
					  "name": "perplexica-frontend",
 | 
				
			||||||
  "version": "1.11.2",
 | 
					  "version": "1.10.2",
 | 
				
			||||||
  "license": "MIT",
 | 
					  "license": "MIT",
 | 
				
			||||||
  "author": "ItzCrazyKns",
 | 
					  "author": "ItzCrazyKns",
 | 
				
			||||||
  "scripts": {
 | 
					  "scripts": {
 | 
				
			||||||
    "dev": "next dev",
 | 
					    "dev": "next dev",
 | 
				
			||||||
    "build": "next build",
 | 
					    "build": "npm run db:push && next build",
 | 
				
			||||||
    "start": "next start",
 | 
					    "start": "next start",
 | 
				
			||||||
    "lint": "next lint",
 | 
					    "lint": "next lint",
 | 
				
			||||||
    "format:write": "prettier . --write"
 | 
					    "format:write": "prettier . --write",
 | 
				
			||||||
 | 
					    "db:push": "drizzle-kit push"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "dependencies": {
 | 
					  "dependencies": {
 | 
				
			||||||
    "@headlessui/react": "^2.2.0",
 | 
					    "@headlessui/react": "^2.2.0",
 | 
				
			||||||
    "@headlessui/tailwindcss": "^0.2.2",
 | 
					 | 
				
			||||||
    "@huggingface/transformers": "^3.7.5",
 | 
					 | 
				
			||||||
    "@iarna/toml": "^2.2.5",
 | 
					    "@iarna/toml": "^2.2.5",
 | 
				
			||||||
    "@icons-pack/react-simple-icons": "^12.3.0",
 | 
					    "@icons-pack/react-simple-icons": "^12.3.0",
 | 
				
			||||||
    "@langchain/anthropic": "^1.0.0",
 | 
					    "@langchain/anthropic": "^0.3.15",
 | 
				
			||||||
    "@langchain/community": "^1.0.0",
 | 
					    "@langchain/community": "^0.3.36",
 | 
				
			||||||
    "@langchain/core": "^1.0.1",
 | 
					    "@langchain/core": "^0.3.42",
 | 
				
			||||||
    "@langchain/google-genai": "^1.0.0",
 | 
					    "@langchain/google-genai": "^0.1.12",
 | 
				
			||||||
    "@langchain/groq": "^1.0.0",
 | 
					    "@langchain/openai": "^0.0.25",
 | 
				
			||||||
    "@langchain/ollama": "^1.0.0",
 | 
					    "@langchain/textsplitters": "^0.1.0",
 | 
				
			||||||
    "@langchain/openai": "^1.0.0",
 | 
					 | 
				
			||||||
    "@langchain/textsplitters": "^1.0.0",
 | 
					 | 
				
			||||||
    "@tailwindcss/typography": "^0.5.12",
 | 
					    "@tailwindcss/typography": "^0.5.12",
 | 
				
			||||||
 | 
					    "@xenova/transformers": "^2.17.2",
 | 
				
			||||||
    "axios": "^1.8.3",
 | 
					    "axios": "^1.8.3",
 | 
				
			||||||
    "better-sqlite3": "^11.9.1",
 | 
					    "better-sqlite3": "^11.9.1",
 | 
				
			||||||
    "clsx": "^2.1.0",
 | 
					    "clsx": "^2.1.0",
 | 
				
			||||||
    "compute-cosine-similarity": "^1.1.0",
 | 
					    "compute-cosine-similarity": "^1.1.0",
 | 
				
			||||||
 | 
					    "compute-dot": "^1.1.0",
 | 
				
			||||||
    "drizzle-orm": "^0.40.1",
 | 
					    "drizzle-orm": "^0.40.1",
 | 
				
			||||||
    "framer-motion": "^12.23.24",
 | 
					 | 
				
			||||||
    "html-to-text": "^9.0.5",
 | 
					    "html-to-text": "^9.0.5",
 | 
				
			||||||
    "jspdf": "^3.0.1",
 | 
					    "langchain": "^0.1.30",
 | 
				
			||||||
    "langchain": "^1.0.1",
 | 
					 | 
				
			||||||
    "lucide-react": "^0.363.0",
 | 
					    "lucide-react": "^0.363.0",
 | 
				
			||||||
    "mammoth": "^1.9.1",
 | 
					 | 
				
			||||||
    "markdown-to-jsx": "^7.7.2",
 | 
					    "markdown-to-jsx": "^7.7.2",
 | 
				
			||||||
    "next": "^15.2.2",
 | 
					    "next": "^15.2.2",
 | 
				
			||||||
    "next-themes": "^0.3.0",
 | 
					    "next-themes": "^0.3.0",
 | 
				
			||||||
@@ -53,8 +49,7 @@
 | 
				
			|||||||
  "devDependencies": {
 | 
					  "devDependencies": {
 | 
				
			||||||
    "@types/better-sqlite3": "^7.6.12",
 | 
					    "@types/better-sqlite3": "^7.6.12",
 | 
				
			||||||
    "@types/html-to-text": "^9.0.4",
 | 
					    "@types/html-to-text": "^9.0.4",
 | 
				
			||||||
    "@types/jspdf": "^2.0.0",
 | 
					    "@types/node": "^20",
 | 
				
			||||||
    "@types/node": "^24.8.1",
 | 
					 | 
				
			||||||
    "@types/pdf-parse": "^1.1.4",
 | 
					    "@types/pdf-parse": "^1.1.4",
 | 
				
			||||||
    "@types/react": "^18",
 | 
					    "@types/react": "^18",
 | 
				
			||||||
    "@types/react-dom": "^18",
 | 
					    "@types/react-dom": "^18",
 | 
				
			||||||
@@ -65,6 +60,6 @@
 | 
				
			|||||||
    "postcss": "^8",
 | 
					    "postcss": "^8",
 | 
				
			||||||
    "prettier": "^3.2.5",
 | 
					    "prettier": "^3.2.5",
 | 
				
			||||||
    "tailwindcss": "^3.3.0",
 | 
					    "tailwindcss": "^3.3.0",
 | 
				
			||||||
    "typescript": "^5.9.3"
 | 
					    "typescript": "^5"
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
| 
		 Before Width: | Height: | Size: 916 B  | 
| 
		 Before Width: | Height: | Size: 515 B  | 
							
								
								
									
										
											BIN
										
									
								
								public/icon.png
									
									
									
									
									
								
							
							
						
						| 
		 Before Width: | Height: | Size: 30 KiB  | 
| 
		 Before Width: | Height: | Size: 183 KiB  | 
| 
		 Before Width: | Height: | Size: 130 KiB  | 
| 
		 Before Width: | Height: | Size: 627 KiB  | 
| 
		 Before Width: | Height: | Size: 202 KiB  | 
@@ -1,131 +0,0 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
					 | 
				
			||||||
<!-- (c) ammap.com | SVG weather icons -->
 | 
					 | 
				
			||||||
<svg width="56" height="48" version="1.1" xmlns="http://www.w3.org/2000/svg">
 | 
					 | 
				
			||||||
  <defs>
 | 
					 | 
				
			||||||
    <filter id="blur" x="-.34167" y="-.34167" width="1.6833" height="1.85">
 | 
					 | 
				
			||||||
      <feGaussianBlur in="SourceAlpha" stdDeviation="3" />
 | 
					 | 
				
			||||||
      <feOffset dx="0" dy="4" result="offsetblur" />
 | 
					 | 
				
			||||||
      <feComponentTransfer>
 | 
					 | 
				
			||||||
        <feFuncA slope="0.05" type="linear" />
 | 
					 | 
				
			||||||
      </feComponentTransfer>
 | 
					 | 
				
			||||||
      <feMerge>
 | 
					 | 
				
			||||||
        <feMergeNode />
 | 
					 | 
				
			||||||
        <feMergeNode in="SourceGraphic" />
 | 
					 | 
				
			||||||
      </feMerge>
 | 
					 | 
				
			||||||
    </filter>
 | 
					 | 
				
			||||||
    <style type="text/css">
 | 
					 | 
				
			||||||
      <![CDATA[
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** SUN
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-sun {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          -webkit-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -moz-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -ms-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          transform: rotate(0deg);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          -webkit-transform: rotate(360deg);
 | 
					 | 
				
			||||||
          -moz-transform: rotate(360deg);
 | 
					 | 
				
			||||||
          -ms-transform: rotate(360deg);
 | 
					 | 
				
			||||||
          transform: rotate(360deg);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-sun {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-sun;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-sun;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-sun;
 | 
					 | 
				
			||||||
        animation-name: am-weather-sun;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 9s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 9s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 9s;
 | 
					 | 
				
			||||||
        animation-duration: 9s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      @keyframes am-weather-sun-shiny {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          stroke-dasharray: 3px 10px;
 | 
					 | 
				
			||||||
          stroke-dashoffset: 0px;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        50% {
 | 
					 | 
				
			||||||
          stroke-dasharray: 0.1px 10px;
 | 
					 | 
				
			||||||
          stroke-dashoffset: -1px;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          stroke-dasharray: 3px 10px;
 | 
					 | 
				
			||||||
          stroke-dashoffset: 0px;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-sun-shiny line {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-sun-shiny;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-sun-shiny;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-sun-shiny;
 | 
					 | 
				
			||||||
        animation-name: am-weather-sun-shiny;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 2s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 2s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 2s;
 | 
					 | 
				
			||||||
        animation-duration: 2s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      ]]>
 | 
					 | 
				
			||||||
    </style>
 | 
					 | 
				
			||||||
  </defs>
 | 
					 | 
				
			||||||
  <g transform="translate(16,-2)" filter="url(#blur)">
 | 
					 | 
				
			||||||
    <g transform="translate(0,16)">
 | 
					 | 
				
			||||||
      <g class="am-weather-sun"
 | 
					 | 
				
			||||||
        style="-moz-animation-duration:9s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-sun;-moz-animation-timing-function:linear;-ms-animation-duration:9s;-ms-animation-iteration-count:infinite;-ms-animation-name:am-weather-sun;-ms-animation-timing-function:linear;-webkit-animation-duration:9s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-sun;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
        <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round" stroke-width="2" />
 | 
					 | 
				
			||||||
        <g transform="rotate(45)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="rotate(90)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="rotate(135)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="scale(-1)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="rotate(225)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="rotate(-90)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="rotate(-45)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <circle r="5" fill="#ffa500" stroke="#ffa500" stroke-width="2" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
  </g>
 | 
					 | 
				
			||||||
</svg>
 | 
					 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 4.9 KiB  | 
@@ -1,159 +0,0 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
					 | 
				
			||||||
<!-- (c) ammap.com | SVG weather icons -->
 | 
					 | 
				
			||||||
<svg width="56" height="48" version="1.1" xmlns="http://www.w3.org/2000/svg">
 | 
					 | 
				
			||||||
  <defs>
 | 
					 | 
				
			||||||
    <filter id="blur" x="-.3038" y="-.3318" width="1.6076" height="1.894">
 | 
					 | 
				
			||||||
      <feGaussianBlur in="SourceAlpha" stdDeviation="3" />
 | 
					 | 
				
			||||||
      <feOffset dx="0" dy="4" result="offsetblur" />
 | 
					 | 
				
			||||||
      <feComponentTransfer>
 | 
					 | 
				
			||||||
        <feFuncA slope="0.05" type="linear" />
 | 
					 | 
				
			||||||
      </feComponentTransfer>
 | 
					 | 
				
			||||||
      <feMerge>
 | 
					 | 
				
			||||||
        <feMergeNode />
 | 
					 | 
				
			||||||
        <feMergeNode in="SourceGraphic" />
 | 
					 | 
				
			||||||
      </feMerge>
 | 
					 | 
				
			||||||
    </filter>
 | 
					 | 
				
			||||||
    <style type="text/css">
 | 
					 | 
				
			||||||
      <![CDATA[
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** MOON
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-moon {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          -webkit-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -moz-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -ms-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          transform: rotate(0deg);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        50% {
 | 
					 | 
				
			||||||
          -webkit-transform: rotate(15deg);
 | 
					 | 
				
			||||||
          -moz-transform: rotate(15deg);
 | 
					 | 
				
			||||||
          -ms-transform: rotate(15deg);
 | 
					 | 
				
			||||||
          transform: rotate(15deg);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          -webkit-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -moz-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -ms-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          transform: rotate(0deg);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-moon {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-moon;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-moon;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-moon;
 | 
					 | 
				
			||||||
        animation-name: am-weather-moon;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 6s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 6s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 6s;
 | 
					 | 
				
			||||||
        animation-duration: 6s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -webkit-transform-origin: 12.5px 15.15px 0;
 | 
					 | 
				
			||||||
        /* TODO FF CENTER ISSUE */
 | 
					 | 
				
			||||||
        -moz-transform-origin: 12.5px 15.15px 0;
 | 
					 | 
				
			||||||
        /* TODO FF CENTER ISSUE */
 | 
					 | 
				
			||||||
        -ms-transform-origin: 12.5px 15.15px 0;
 | 
					 | 
				
			||||||
        /* TODO FF CENTER ISSUE */
 | 
					 | 
				
			||||||
        transform-origin: 12.5px 15.15px 0;
 | 
					 | 
				
			||||||
        /* TODO FF CENTER ISSUE */
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      @keyframes am-weather-moon-star-1 {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          opacity: 0;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          opacity: 1;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-moon-star-1 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-moon-star-1;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-moon-star-1;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-moon-star-1;
 | 
					 | 
				
			||||||
        animation-name: am-weather-moon-star-1;
 | 
					 | 
				
			||||||
        -webkit-animation-delay: 3s;
 | 
					 | 
				
			||||||
        -moz-animation-delay: 3s;
 | 
					 | 
				
			||||||
        -ms-animation-delay: 3s;
 | 
					 | 
				
			||||||
        animation-delay: 3s;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 5s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 5s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 5s;
 | 
					 | 
				
			||||||
        animation-duration: 5s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        animation-iteration-count: 1;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      @keyframes am-weather-moon-star-2 {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          opacity: 0;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          opacity: 1;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-moon-star-2 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-moon-star-2;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-moon-star-2;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-moon-star-2;
 | 
					 | 
				
			||||||
        animation-name: am-weather-moon-star-2;
 | 
					 | 
				
			||||||
        -webkit-animation-delay: 5s;
 | 
					 | 
				
			||||||
        -moz-animation-delay: 5s;
 | 
					 | 
				
			||||||
        -ms-animation-delay: 5s;
 | 
					 | 
				
			||||||
        animation-delay: 5s;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 4s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 4s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 4s;
 | 
					 | 
				
			||||||
        animation-duration: 4s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        animation-iteration-count: 1;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      ]]>
 | 
					 | 
				
			||||||
    </style>
 | 
					 | 
				
			||||||
  </defs>
 | 
					 | 
				
			||||||
  <g id="night" transform="translate(-4,-18)" filter="url(#blur)">
 | 
					 | 
				
			||||||
    <g transform="matrix(.8 0 0 .78534 36 20.022)" stroke-width="1.2616">
 | 
					 | 
				
			||||||
      <g class="am-weather-moon-star-1"
 | 
					 | 
				
			||||||
        style="-moz-animation-delay:3s;-moz-animation-duration:5s;-moz-animation-iteration-count:1;-moz-animation-name:am-weather-moon-star-1;-moz-animation-timing-function:linear;-ms-animation-delay:3s;-ms-animation-duration:5s;-ms-animation-iteration-count:1;-ms-animation-name:am-weather-moon-star-1;-ms-animation-timing-function:linear;-webkit-animation-delay:3s;-webkit-animation-duration:5s;-webkit-animation-iteration-count:1;-webkit-animation-name:am-weather-moon-star-1;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
        <polygon points="4 2.7 5.2 3.3 4 4 3.3 5.2 2.7 4 1.5 3.3 2.7 2.7 3.3 1.5" fill="#ffa500" stroke-miterlimit="10"
 | 
					 | 
				
			||||||
          stroke-width="1.4105" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
      <g class="am-weather-moon-star-2"
 | 
					 | 
				
			||||||
        style="-moz-animation-delay:5s;-moz-animation-duration:4s;-moz-animation-iteration-count:1;-moz-animation-name:am-weather-moon-star-2;-moz-animation-timing-function:linear;-ms-animation-delay:5s;-ms-animation-duration:4s;-ms-animation-iteration-count:1;-ms-animation-name:am-weather-moon-star-2;-ms-animation-timing-function:linear;-webkit-animation-delay:5s;-webkit-animation-duration:4s;-webkit-animation-iteration-count:1;-webkit-animation-name:am-weather-moon-star-2;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
        <polygon transform="translate(20,10)" points="4 2.7 5.2 3.3 4 4 3.3 5.2 2.7 4 1.5 3.3 2.7 2.7 3.3 1.5"
 | 
					 | 
				
			||||||
          fill="#ffa500" stroke-miterlimit="10" stroke-width="1.4105" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
      <g class="am-weather-moon"
 | 
					 | 
				
			||||||
        style="-moz-animation-duration:6s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-moon;-moz-animation-timing-function:linear;-moz-transform-origin:12.5px 15.15px 0;-ms-animation-duration:6s;-ms-animation-iteration-count:infinite;-ms-animation-name:am-weather-moon;-ms-animation-timing-function:linear;-ms-transform-origin:12.5px 15.15px 0;-webkit-animation-duration:6s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-moon;-webkit-animation-timing-function:linear;-webkit-transform-origin:12.5px 15.15px 0">
 | 
					 | 
				
			||||||
        <path
 | 
					 | 
				
			||||||
          d="m14.5 13.2c0-3.7 2-6.9 5-8.7-1.5-0.9-3.2-1.3-5-1.3-5.5 0-10 4.5-10 10s4.5 10 10 10c1.8 0 3.5-0.5 5-1.3-3-1.7-5-5-5-8.7z"
 | 
					 | 
				
			||||||
          fill="#ffa500" stroke="#ffa500" stroke-linejoin="round" stroke-width="2.5232" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
  </g>
 | 
					 | 
				
			||||||
</svg>
 | 
					 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 6.7 KiB  | 
@@ -1,178 +0,0 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
					 | 
				
			||||||
<!-- (c) ammap.com | SVG weather icons -->
 | 
					 | 
				
			||||||
<svg width="56" height="48" version="1.1" xmlns="http://www.w3.org/2000/svg">
 | 
					 | 
				
			||||||
  <defs>
 | 
					 | 
				
			||||||
    <filter id="blur" x="-.20655" y="-.28472" width="1.403" height="1.6944">
 | 
					 | 
				
			||||||
      <feGaussianBlur in="SourceAlpha" stdDeviation="3" />
 | 
					 | 
				
			||||||
      <feOffset dx="0" dy="4" result="offsetblur" />
 | 
					 | 
				
			||||||
      <feComponentTransfer>
 | 
					 | 
				
			||||||
        <feFuncA slope="0.05" type="linear" />
 | 
					 | 
				
			||||||
      </feComponentTransfer>
 | 
					 | 
				
			||||||
      <feMerge>
 | 
					 | 
				
			||||||
        <feMergeNode />
 | 
					 | 
				
			||||||
        <feMergeNode in="SourceGraphic" />
 | 
					 | 
				
			||||||
      </feMerge>
 | 
					 | 
				
			||||||
    </filter>
 | 
					 | 
				
			||||||
    <style type="text/css">
 | 
					 | 
				
			||||||
      <![CDATA[
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** CLOUDS
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-cloud-2 {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        50% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-cloud-2 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-cloud-2;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-cloud-2;
 | 
					 | 
				
			||||||
        animation-name: am-weather-cloud-2;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 3s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 3s;
 | 
					 | 
				
			||||||
        animation-duration: 3s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** SUN
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-sun {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          -webkit-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -moz-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -ms-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          transform: rotate(0deg);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          -webkit-transform: rotate(360deg);
 | 
					 | 
				
			||||||
          -moz-transform: rotate(360deg);
 | 
					 | 
				
			||||||
          -ms-transform: rotate(360deg);
 | 
					 | 
				
			||||||
          transform: rotate(360deg);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-sun {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-sun;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-sun;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-sun;
 | 
					 | 
				
			||||||
        animation-name: am-weather-sun;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 9s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 9s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 9s;
 | 
					 | 
				
			||||||
        animation-duration: 9s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      @keyframes am-weather-sun-shiny {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          stroke-dasharray: 3px 10px;
 | 
					 | 
				
			||||||
          stroke-dashoffset: 0px;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        50% {
 | 
					 | 
				
			||||||
          stroke-dasharray: 0.1px 10px;
 | 
					 | 
				
			||||||
          stroke-dashoffset: -1px;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          stroke-dasharray: 3px 10px;
 | 
					 | 
				
			||||||
          stroke-dashoffset: 0px;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-sun-shiny line {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-sun-shiny;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-sun-shiny;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-sun-shiny;
 | 
					 | 
				
			||||||
        animation-name: am-weather-sun-shiny;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 2s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 2s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 2s;
 | 
					 | 
				
			||||||
        animation-duration: 2s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      ]]>
 | 
					 | 
				
			||||||
    </style>
 | 
					 | 
				
			||||||
  </defs>
 | 
					 | 
				
			||||||
  <g transform="translate(16,-2)" filter="url(#blur)">
 | 
					 | 
				
			||||||
    <g transform="translate(0,16)">
 | 
					 | 
				
			||||||
      <g class="am-weather-sun"
 | 
					 | 
				
			||||||
        style="-moz-animation-duration:9s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-sun;-moz-animation-timing-function:linear;-ms-animation-duration:9s;-ms-animation-iteration-count:infinite;-ms-animation-name:am-weather-sun;-ms-animation-timing-function:linear;-webkit-animation-duration:9s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-sun;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
        <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round" stroke-width="2" />
 | 
					 | 
				
			||||||
        <g transform="rotate(45)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="rotate(90)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="rotate(135)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="scale(-1)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="rotate(225)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="rotate(-90)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="rotate(-45)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <circle r="5" fill="#ffa500" stroke="#ffa500" stroke-width="2" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
    <g class="am-weather-cloud-2"
 | 
					 | 
				
			||||||
      style="-moz-animation-duration:3s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-cloud-2;-moz-animation-timing-function:linear;-webkit-animation-duration:3s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-cloud-2;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
      <path transform="translate(-20,-11)"
 | 
					 | 
				
			||||||
        d="m47.7 35.4c0-4.6-3.7-8.2-8.2-8.2-1 0-1.9 0.2-2.8 0.5-0.3-3.4-3.1-6.2-6.6-6.2-3.7 0-6.7 3-6.7 6.7 0 0.8 0.2 1.6 0.4 2.3-0.3-0.1-0.7-0.1-1-0.1-3.7 0-6.7 3-6.7 6.7 0 3.6 2.9 6.6 6.5 6.7h17.2c4.4-0.5 7.9-4 7.9-8.4z"
 | 
					 | 
				
			||||||
        fill="#c6deff" stroke="#fff" stroke-linejoin="round" stroke-width="1.2" />
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
  </g>
 | 
					 | 
				
			||||||
</svg>
 | 
					 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 6.8 KiB  | 
@@ -1,206 +0,0 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
					 | 
				
			||||||
<!-- (c) ammap.com | SVG weather icons -->
 | 
					 | 
				
			||||||
<svg width="56" height="48" version="1.1" xmlns="http://www.w3.org/2000/svg">
 | 
					 | 
				
			||||||
  <defs>
 | 
					 | 
				
			||||||
    <filter id="blur" x="-.19471" y="-.26087" width="1.3744" height="1.6884">
 | 
					 | 
				
			||||||
      <feGaussianBlur in="SourceAlpha" stdDeviation="3" />
 | 
					 | 
				
			||||||
      <feOffset dx="0" dy="4" result="offsetblur" />
 | 
					 | 
				
			||||||
      <feComponentTransfer>
 | 
					 | 
				
			||||||
        <feFuncA slope="0.05" type="linear" />
 | 
					 | 
				
			||||||
      </feComponentTransfer>
 | 
					 | 
				
			||||||
      <feMerge>
 | 
					 | 
				
			||||||
        <feMergeNode />
 | 
					 | 
				
			||||||
        <feMergeNode in="SourceGraphic" />
 | 
					 | 
				
			||||||
      </feMerge>
 | 
					 | 
				
			||||||
    </filter>
 | 
					 | 
				
			||||||
    <style type="text/css">
 | 
					 | 
				
			||||||
      <![CDATA[
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** CLOUDS
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-cloud-2 {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        50% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-cloud-2 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-cloud-2;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-cloud-2;
 | 
					 | 
				
			||||||
        animation-name: am-weather-cloud-2;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 3s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 3s;
 | 
					 | 
				
			||||||
        animation-duration: 3s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** MOON
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-moon {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          -webkit-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -moz-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -ms-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          transform: rotate(0deg);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        50% {
 | 
					 | 
				
			||||||
          -webkit-transform: rotate(15deg);
 | 
					 | 
				
			||||||
          -moz-transform: rotate(15deg);
 | 
					 | 
				
			||||||
          -ms-transform: rotate(15deg);
 | 
					 | 
				
			||||||
          transform: rotate(15deg);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          -webkit-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -moz-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -ms-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          transform: rotate(0deg);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-moon {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-moon;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-moon;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-moon;
 | 
					 | 
				
			||||||
        animation-name: am-weather-moon;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 6s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 6s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 6s;
 | 
					 | 
				
			||||||
        animation-duration: 6s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -webkit-transform-origin: 12.5px 15.15px 0;
 | 
					 | 
				
			||||||
        /* TODO FF CENTER ISSUE */
 | 
					 | 
				
			||||||
        -moz-transform-origin: 12.5px 15.15px 0;
 | 
					 | 
				
			||||||
        /* TODO FF CENTER ISSUE */
 | 
					 | 
				
			||||||
        -ms-transform-origin: 12.5px 15.15px 0;
 | 
					 | 
				
			||||||
        /* TODO FF CENTER ISSUE */
 | 
					 | 
				
			||||||
        transform-origin: 12.5px 15.15px 0;
 | 
					 | 
				
			||||||
        /* TODO FF CENTER ISSUE */
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      @keyframes am-weather-moon-star-1 {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          opacity: 0;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          opacity: 1;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-moon-star-1 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-moon-star-1;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-moon-star-1;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-moon-star-1;
 | 
					 | 
				
			||||||
        animation-name: am-weather-moon-star-1;
 | 
					 | 
				
			||||||
        -webkit-animation-delay: 3s;
 | 
					 | 
				
			||||||
        -moz-animation-delay: 3s;
 | 
					 | 
				
			||||||
        -ms-animation-delay: 3s;
 | 
					 | 
				
			||||||
        animation-delay: 3s;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 5s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 5s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 5s;
 | 
					 | 
				
			||||||
        animation-duration: 5s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        animation-iteration-count: 1;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      @keyframes am-weather-moon-star-2 {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          opacity: 0;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          opacity: 1;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-moon-star-2 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-moon-star-2;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-moon-star-2;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-moon-star-2;
 | 
					 | 
				
			||||||
        animation-name: am-weather-moon-star-2;
 | 
					 | 
				
			||||||
        -webkit-animation-delay: 5s;
 | 
					 | 
				
			||||||
        -moz-animation-delay: 5s;
 | 
					 | 
				
			||||||
        -ms-animation-delay: 5s;
 | 
					 | 
				
			||||||
        animation-delay: 5s;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 4s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 4s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 4s;
 | 
					 | 
				
			||||||
        animation-duration: 4s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        animation-iteration-count: 1;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      ]]>
 | 
					 | 
				
			||||||
    </style>
 | 
					 | 
				
			||||||
  </defs>
 | 
					 | 
				
			||||||
  <g transform="translate(16,-2)" filter="url(#blur)">
 | 
					 | 
				
			||||||
    <g transform="matrix(.8 0 0 .8 16 4)">
 | 
					 | 
				
			||||||
      <g class="am-weather-moon-star-1"
 | 
					 | 
				
			||||||
        style="-moz-animation-delay:3s;-moz-animation-duration:5s;-moz-animation-iteration-count:1;-moz-animation-name:am-weather-moon-star-1;-moz-animation-timing-function:linear;-ms-animation-delay:3s;-ms-animation-duration:5s;-ms-animation-iteration-count:1;-ms-animation-name:am-weather-moon-star-1;-ms-animation-timing-function:linear;-webkit-animation-delay:3s;-webkit-animation-duration:5s;-webkit-animation-iteration-count:1;-webkit-animation-name:am-weather-moon-star-1;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
        <polygon points="1.5 3.3 2.7 2.7 3.3 1.5 4 2.7 5.2 3.3 4 4 3.3 5.2 2.7 4" fill="#ffa500"
 | 
					 | 
				
			||||||
          stroke-miterlimit="10" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
      <g class="am-weather-moon-star-2"
 | 
					 | 
				
			||||||
        style="-moz-animation-delay:5s;-moz-animation-duration:4s;-moz-animation-iteration-count:1;-moz-animation-name:am-weather-moon-star-2;-moz-animation-timing-function:linear;-ms-animation-delay:5s;-ms-animation-duration:4s;-ms-animation-iteration-count:1;-ms-animation-name:am-weather-moon-star-2;-ms-animation-timing-function:linear;-webkit-animation-delay:5s;-webkit-animation-duration:4s;-webkit-animation-iteration-count:1;-webkit-animation-name:am-weather-moon-star-2;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
        <polygon transform="translate(20,10)" points="1.5 3.3 2.7 2.7 3.3 1.5 4 2.7 5.2 3.3 4 4 3.3 5.2 2.7 4"
 | 
					 | 
				
			||||||
          fill="#ffa500" stroke-miterlimit="10" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
      <g class="am-weather-moon"
 | 
					 | 
				
			||||||
        style="-moz-animation-duration:6s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-moon;-moz-animation-timing-function:linear;-moz-transform-origin:12.5px 15.15px 0;-ms-animation-duration:6s;-ms-animation-iteration-count:infinite;-ms-animation-name:am-weather-moon;-ms-animation-timing-function:linear;-ms-transform-origin:12.5px 15.15px 0;-webkit-animation-duration:6s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-moon;-webkit-animation-timing-function:linear;-webkit-transform-origin:12.5px 15.15px 0">
 | 
					 | 
				
			||||||
        <path
 | 
					 | 
				
			||||||
          d="m14.5 13.2c0-3.7 2-6.9 5-8.7-1.5-0.9-3.2-1.3-5-1.3-5.5 0-10 4.5-10 10s4.5 10 10 10c1.8 0 3.5-0.5 5-1.3-3-1.7-5-5-5-8.7z"
 | 
					 | 
				
			||||||
          fill="#ffa500" stroke="#ffa500" stroke-linejoin="round" stroke-width="2" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
    <g class="am-weather-cloud-2"
 | 
					 | 
				
			||||||
      style="-moz-animation-duration:3s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-cloud-2;-moz-animation-timing-function:linear;-webkit-animation-duration:3s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-cloud-2;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
      <path transform="translate(-20,-11)"
 | 
					 | 
				
			||||||
        d="m47.7 35.4c0-4.6-3.7-8.2-8.2-8.2-1 0-1.9 0.2-2.8 0.5-0.3-3.4-3.1-6.2-6.6-6.2-3.7 0-6.7 3-6.7 6.7 0 0.8 0.2 1.6 0.4 2.3-0.3-0.1-0.7-0.1-1-0.1-3.7 0-6.7 3-6.7 6.7 0 3.6 2.9 6.6 6.5 6.7h17.2c4.4-0.5 7.9-4 7.9-8.4z"
 | 
					 | 
				
			||||||
        fill="#c6deff" stroke="#fff" stroke-linejoin="round" stroke-width="1.2" />
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
  </g>
 | 
					 | 
				
			||||||
</svg>
 | 
					 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 8.6 KiB  | 
@@ -1,244 +0,0 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
					 | 
				
			||||||
<!-- (c) ammap.com | SVG weather icons -->
 | 
					 | 
				
			||||||
<svg width="56" height="48" version="1.1" xmlns="http://www.w3.org/2000/svg">
 | 
					 | 
				
			||||||
  <defs>
 | 
					 | 
				
			||||||
    <filter id="blur" x="-.20655" y="-.21122" width="1.403" height="1.4997">
 | 
					 | 
				
			||||||
      <feGaussianBlur in="SourceAlpha" stdDeviation="3" />
 | 
					 | 
				
			||||||
      <feOffset dx="0" dy="4" result="offsetblur" />
 | 
					 | 
				
			||||||
      <feComponentTransfer>
 | 
					 | 
				
			||||||
        <feFuncA slope="0.05" type="linear" />
 | 
					 | 
				
			||||||
      </feComponentTransfer>
 | 
					 | 
				
			||||||
      <feMerge>
 | 
					 | 
				
			||||||
        <feMergeNode />
 | 
					 | 
				
			||||||
        <feMergeNode in="SourceGraphic" />
 | 
					 | 
				
			||||||
      </feMerge>
 | 
					 | 
				
			||||||
    </filter>
 | 
					 | 
				
			||||||
    <style type="text/css">
 | 
					 | 
				
			||||||
      <![CDATA[
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** SUN
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-sun {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          -webkit-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -moz-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -ms-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          transform: rotate(0deg);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          -webkit-transform: rotate(360deg);
 | 
					 | 
				
			||||||
          -moz-transform: rotate(360deg);
 | 
					 | 
				
			||||||
          -ms-transform: rotate(360deg);
 | 
					 | 
				
			||||||
          transform: rotate(360deg);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-sun {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-sun;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-sun;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-sun;
 | 
					 | 
				
			||||||
        animation-name: am-weather-sun;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 9s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 9s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 9s;
 | 
					 | 
				
			||||||
        animation-duration: 9s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** FOG
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-fog-1 {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          transform: translate(0px, 0px)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        50% {
 | 
					 | 
				
			||||||
          transform: translate(7px, 0px)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          transform: translate(0px, 0px)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-fog-1 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-fog-1;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-fog-1;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-fog-1;
 | 
					 | 
				
			||||||
        animation-name: am-weather-fog-1;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 8s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 8s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 8s;
 | 
					 | 
				
			||||||
        animation-duration: 8s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      @keyframes am-weather-fog-2 {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          transform: translate(0px, 0px)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        21.05% {
 | 
					 | 
				
			||||||
          transform: translate(-6px, 0px)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        78.95% {
 | 
					 | 
				
			||||||
          transform: translate(9px, 0px)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          transform: translate(0px, 0px)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-fog-2 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-fog-2;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-fog-2;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-fog-2;
 | 
					 | 
				
			||||||
        animation-name: am-weather-fog-2;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 20s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 20s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 20s;
 | 
					 | 
				
			||||||
        animation-duration: 20s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      @keyframes am-weather-fog-3 {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          transform: translate(0px, 0px)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        25% {
 | 
					 | 
				
			||||||
          transform: translate(4px, 0px)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        75% {
 | 
					 | 
				
			||||||
          transform: translate(-4px, 0px)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          transform: translate(0px, 0px)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-fog-3 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-fog-3;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-fog-3;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-fog-3;
 | 
					 | 
				
			||||||
        animation-name: am-weather-fog-3;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 6s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 6s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 6s;
 | 
					 | 
				
			||||||
        animation-duration: 6s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      @keyframes am-weather-fog-4 {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          transform: translate(0px, 0px)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        50% {
 | 
					 | 
				
			||||||
          transform: translate(-4px, 0px)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          transform: translate(0px, 0px)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-fog-4 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-fog-4;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-fog-4;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-fog-4;
 | 
					 | 
				
			||||||
        animation-name: am-weather-fog-4;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 6s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 6s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 6s;
 | 
					 | 
				
			||||||
        animation-duration: 6s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      ]]>
 | 
					 | 
				
			||||||
    </style>
 | 
					 | 
				
			||||||
  </defs>
 | 
					 | 
				
			||||||
  <g transform="translate(16,-2)" filter="url(#blur)">
 | 
					 | 
				
			||||||
    <g transform="translate(0,16)">
 | 
					 | 
				
			||||||
      <g class="am-weather-sun" transform="translate(0,16)">
 | 
					 | 
				
			||||||
        <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffc04a" stroke-linecap="round" stroke-width="2" />
 | 
					 | 
				
			||||||
        <g transform="rotate(45)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffc04a" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="rotate(90)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffc04a" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="rotate(135)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffc04a" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="scale(-1)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffc04a" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />F
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="rotate(225)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffc04a" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="rotate(-90)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffc04a" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="rotate(-45)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffc04a" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <circle r="5" fill="#ffc04a" stroke="#ffc04a" stroke-width="2" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
    <g class="am-weather-fog" transform="translate(-10,20)" fill="none" stroke="#c6deff" stroke-linecap="round"
 | 
					 | 
				
			||||||
      stroke-width="2">
 | 
					 | 
				
			||||||
      <line class="am-weather-fog-1" y1="0" y2="0" x1="1" x2="37" stroke-dasharray="3, 5, 17, 5, 7" />
 | 
					 | 
				
			||||||
      <line class="am-weather-fog-2" y1="5" y2="5" x1="9" x2="33" stroke-dasharray="11, 7, 15" />
 | 
					 | 
				
			||||||
      <line class="am-weather-fog-3" y1="10" y2="10" x1="5" x2="40" stroke-dasharray="11, 7, 3, 5, 9" />
 | 
					 | 
				
			||||||
      <line class="am-weather-fog-4" y1="15" y2="15" x1="7" x2="42" stroke-dasharray="13, 5, 9, 5, 3" />
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
  </g>
 | 
					 | 
				
			||||||
</svg>
 | 
					 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 8.0 KiB  | 
@@ -1,309 +0,0 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
					 | 
				
			||||||
<!-- (c) ammap.com | SVG weather icons -->
 | 
					 | 
				
			||||||
<svg width="56" height="48" version="1.1" xmlns="http://www.w3.org/2000/svg">
 | 
					 | 
				
			||||||
  <defs>
 | 
					 | 
				
			||||||
    <filter id="blur" x="-.20655" y="-.21122" width="1.403" height="1.4997">
 | 
					 | 
				
			||||||
      <feGaussianBlur in="SourceAlpha" stdDeviation="3" />
 | 
					 | 
				
			||||||
      <feOffset dx="0" dy="4" result="offsetblur" />
 | 
					 | 
				
			||||||
      <feComponentTransfer>
 | 
					 | 
				
			||||||
        <feFuncA slope="0.05" type="linear" />
 | 
					 | 
				
			||||||
      </feComponentTransfer>
 | 
					 | 
				
			||||||
      <feMerge>
 | 
					 | 
				
			||||||
        <feMergeNode />
 | 
					 | 
				
			||||||
        <feMergeNode in="SourceGraphic" />
 | 
					 | 
				
			||||||
      </feMerge>
 | 
					 | 
				
			||||||
    </filter>
 | 
					 | 
				
			||||||
    <style type="text/css">
 | 
					 | 
				
			||||||
      <![CDATA[
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** MOON
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-moon {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          -webkit-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -moz-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -ms-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          transform: rotate(0deg);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        50% {
 | 
					 | 
				
			||||||
          -webkit-transform: rotate(15deg);
 | 
					 | 
				
			||||||
          -moz-transform: rotate(15deg);
 | 
					 | 
				
			||||||
          -ms-transform: rotate(15deg);
 | 
					 | 
				
			||||||
          transform: rotate(15deg);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          -webkit-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -moz-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -ms-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          transform: rotate(0deg);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-moon {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-moon;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-moon;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-moon;
 | 
					 | 
				
			||||||
        animation-name: am-weather-moon;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 6s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 6s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 6s;
 | 
					 | 
				
			||||||
        animation-duration: 6s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -webkit-transform-origin: 12.5px 15.15px 0;
 | 
					 | 
				
			||||||
        /* TODO FF CENTER ISSUE */
 | 
					 | 
				
			||||||
        -moz-transform-origin: 12.5px 15.15px 0;
 | 
					 | 
				
			||||||
        /* TODO FF CENTER ISSUE */
 | 
					 | 
				
			||||||
        -ms-transform-origin: 12.5px 15.15px 0;
 | 
					 | 
				
			||||||
        /* TODO FF CENTER ISSUE */
 | 
					 | 
				
			||||||
        transform-origin: 12.5px 15.15px 0;
 | 
					 | 
				
			||||||
        /* TODO FF CENTER ISSUE */
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      @keyframes am-weather-moon-star-1 {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          opacity: 0;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          opacity: 1;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-moon-star-1 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-moon-star-1;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-moon-star-1;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-moon-star-1;
 | 
					 | 
				
			||||||
        animation-name: am-weather-moon-star-1;
 | 
					 | 
				
			||||||
        -webkit-animation-delay: 3s;
 | 
					 | 
				
			||||||
        -moz-animation-delay: 3s;
 | 
					 | 
				
			||||||
        -ms-animation-delay: 3s;
 | 
					 | 
				
			||||||
        animation-delay: 3s;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 5s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 5s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 5s;
 | 
					 | 
				
			||||||
        animation-duration: 5s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        animation-iteration-count: 1;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      @keyframes am-weather-moon-star-2 {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          opacity: 0;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          opacity: 1;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-moon-star-2 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-moon-star-2;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-moon-star-2;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-moon-star-2;
 | 
					 | 
				
			||||||
        animation-name: am-weather-moon-star-2;
 | 
					 | 
				
			||||||
        -webkit-animation-delay: 5s;
 | 
					 | 
				
			||||||
        -moz-animation-delay: 5s;
 | 
					 | 
				
			||||||
        -ms-animation-delay: 5s;
 | 
					 | 
				
			||||||
        animation-delay: 5s;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 4s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 4s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 4s;
 | 
					 | 
				
			||||||
        animation-duration: 4s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        animation-iteration-count: 1;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** FOG
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-fog-1 {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          transform: translate(0px, 0px)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        50% {
 | 
					 | 
				
			||||||
          transform: translate(7px, 0px)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          transform: translate(0px, 0px)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-fog-1 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-fog-1;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-fog-1;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-fog-1;
 | 
					 | 
				
			||||||
        animation-name: am-weather-fog-1;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 8s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 8s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 8s;
 | 
					 | 
				
			||||||
        animation-duration: 8s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      @keyframes am-weather-fog-2 {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          transform: translate(0px, 0px)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        21.05% {
 | 
					 | 
				
			||||||
          transform: translate(-6px, 0px)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        78.95% {
 | 
					 | 
				
			||||||
          transform: translate(9px, 0px)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          transform: translate(0px, 0px)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-fog-2 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-fog-2;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-fog-2;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-fog-2;
 | 
					 | 
				
			||||||
        animation-name: am-weather-fog-2;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 20s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 20s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 20s;
 | 
					 | 
				
			||||||
        animation-duration: 20s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      @keyframes am-weather-fog-3 {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          transform: translate(0px, 0px)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        25% {
 | 
					 | 
				
			||||||
          transform: translate(4px, 0px)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        75% {
 | 
					 | 
				
			||||||
          transform: translate(-4px, 0px)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          transform: translate(0px, 0px)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-fog-3 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-fog-3;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-fog-3;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-fog-3;
 | 
					 | 
				
			||||||
        animation-name: am-weather-fog-3;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 6s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 6s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 6s;
 | 
					 | 
				
			||||||
        animation-duration: 6s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      @keyframes am-weather-fog-4 {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          transform: translate(0px, 0px)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        50% {
 | 
					 | 
				
			||||||
          transform: translate(-4px, 0px)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          transform: translate(0px, 0px)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-fog-4 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-fog-4;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-fog-4;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-fog-4;
 | 
					 | 
				
			||||||
        animation-name: am-weather-fog-4;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 6s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 6s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 6s;
 | 
					 | 
				
			||||||
        animation-duration: 6s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      ]]>
 | 
					 | 
				
			||||||
    </style>
 | 
					 | 
				
			||||||
  </defs>
 | 
					 | 
				
			||||||
  <g transform="translate(16,-2)" filter="url(#blur)">
 | 
					 | 
				
			||||||
    <g transform="matrix(.8 0 0 .8 16 4)">
 | 
					 | 
				
			||||||
      <g class="am-weather-moon-star-1"
 | 
					 | 
				
			||||||
        style="-moz-animation-delay:3s;-moz-animation-duration:5s;-moz-animation-iteration-count:1;-moz-animation-name:am-weather-moon-star-1;-moz-animation-timing-function:linear;-ms-animation-delay:3s;-ms-animation-duration:5s;-ms-animation-iteration-count:1;-ms-animation-name:am-weather-moon-star-1;-ms-animation-timing-function:linear;-webkit-animation-delay:3s;-webkit-animation-duration:5s;-webkit-animation-iteration-count:1;-webkit-animation-name:am-weather-moon-star-1;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
        <polygon points="4 4 3.3 5.2 2.7 4 1.5 3.3 2.7 2.7 3.3 1.5 4 2.7 5.2 3.3" fill="#ffc04a"
 | 
					 | 
				
			||||||
          stroke-miterlimit="10" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
      <g class="am-weather-moon-star-2"
 | 
					 | 
				
			||||||
        style="-moz-animation-delay:5s;-moz-animation-duration:4s;-moz-animation-iteration-count:1;-moz-animation-name:am-weather-moon-star-2;-moz-animation-timing-function:linear;-ms-animation-delay:5s;-ms-animation-duration:4s;-ms-animation-iteration-count:1;-ms-animation-name:am-weather-moon-star-2;-ms-animation-timing-function:linear;-webkit-animation-delay:5s;-webkit-animation-duration:4s;-webkit-animation-iteration-count:1;-webkit-animation-name:am-weather-moon-star-2;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
        <polygon transform="translate(20,10)" points="4 4 3.3 5.2 2.7 4 1.5 3.3 2.7 2.7 3.3 1.5 4 2.7 5.2 3.3"
 | 
					 | 
				
			||||||
          fill="#ffc04a" stroke-miterlimit="10" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
      <g class="am-weather-moon"
 | 
					 | 
				
			||||||
        style="-moz-animation-duration:6s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-moon;-moz-animation-timing-function:linear;-moz-transform-origin:12.5px 15.15px 0;-ms-animation-duration:6s;-ms-animation-iteration-count:infinite;-ms-animation-name:am-weather-moon;-ms-animation-timing-function:linear;-ms-transform-origin:12.5px 15.15px 0;-webkit-animation-duration:6s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-moon;-webkit-animation-timing-function:linear;-webkit-transform-origin:12.5px 15.15px 0">
 | 
					 | 
				
			||||||
        <path
 | 
					 | 
				
			||||||
          d="m14.5 13.2c0-3.7 2-6.9 5-8.7-1.5-0.9-3.2-1.3-5-1.3-5.5 0-10 4.5-10 10s4.5 10 10 10c1.8 0 3.5-0.5 5-1.3-3-1.7-5-5-5-8.7z"
 | 
					 | 
				
			||||||
          fill="#ffc04a" stroke="#ffc04a" stroke-linejoin="round" stroke-width="2" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
    <g class="am-weather-fog" transform="translate(-10,20)" fill="none" stroke="#c6deff" stroke-linecap="round"
 | 
					 | 
				
			||||||
      stroke-width="2">
 | 
					 | 
				
			||||||
      <line class="am-weather-fog-1" y1="0" y2="0" x1="1" x2="37" stroke-dasharray="3, 5, 17, 5, 7" />
 | 
					 | 
				
			||||||
      <line class="am-weather-fog-2" y1="5" y2="5" x1="9" x2="33" stroke-dasharray="11, 7, 15" />
 | 
					 | 
				
			||||||
      <line class="am-weather-fog-3" y1="10" y2="10" x1="5" x2="40" stroke-dasharray="11, 7, 3, 5, 9" />
 | 
					 | 
				
			||||||
      <line class="am-weather-fog-4" y1="15" y2="15" x1="7" x2="42" stroke-dasharray="13, 5, 9, 5, 3" />
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
  </g>
 | 
					 | 
				
			||||||
</svg>
 | 
					 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 11 KiB  | 
@@ -1,204 +0,0 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
					 | 
				
			||||||
<!-- (c) ammap.com | SVG weather icons -->
 | 
					 | 
				
			||||||
<svg width="56" height="48" version="1.1" xmlns="http://www.w3.org/2000/svg">
 | 
					 | 
				
			||||||
  <defs>
 | 
					 | 
				
			||||||
    <filter id="blur" x="-.20655" y="-.21122" width="1.403" height="1.4997">
 | 
					 | 
				
			||||||
      <feGaussianBlur in="SourceAlpha" stdDeviation="3" />
 | 
					 | 
				
			||||||
      <feOffset dx="0" dy="4" result="offsetblur" />
 | 
					 | 
				
			||||||
      <feComponentTransfer>
 | 
					 | 
				
			||||||
        <feFuncA slope="0.05" type="linear" />
 | 
					 | 
				
			||||||
      </feComponentTransfer>
 | 
					 | 
				
			||||||
      <feMerge>
 | 
					 | 
				
			||||||
        <feMergeNode />
 | 
					 | 
				
			||||||
        <feMergeNode in="SourceGraphic" />
 | 
					 | 
				
			||||||
      </feMerge>
 | 
					 | 
				
			||||||
    </filter>
 | 
					 | 
				
			||||||
    <style type="text/css">
 | 
					 | 
				
			||||||
      <![CDATA[
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** SUN
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-sun {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          -webkit-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -moz-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -ms-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          transform: rotate(0deg);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          -webkit-transform: rotate(360deg);
 | 
					 | 
				
			||||||
          -moz-transform: rotate(360deg);
 | 
					 | 
				
			||||||
          -ms-transform: rotate(360deg);
 | 
					 | 
				
			||||||
          transform: rotate(360deg);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-sun {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-sun;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-sun;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-sun;
 | 
					 | 
				
			||||||
        animation-name: am-weather-sun;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 9s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 9s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 9s;
 | 
					 | 
				
			||||||
        animation-duration: 9s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** FROST
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-frost {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        1% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        3% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(-0.3px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(-0.3px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(-0.3px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(-0.3px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        5% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        7% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(-0.3px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(-0.3px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(-0.3px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(-0.3px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        9% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        11% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(-0.3px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(-0.3px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(-0.3px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(-0.3px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        13% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        15% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(-0.3px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(-0.3px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(-0.3px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(-0.3px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        16% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-frost {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-frost;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-frost;
 | 
					 | 
				
			||||||
        animation-name: am-weather-frost;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 1.11s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 1.11s;
 | 
					 | 
				
			||||||
        animation-duration: 1.11s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      ]]>
 | 
					 | 
				
			||||||
    </style>
 | 
					 | 
				
			||||||
  </defs>
 | 
					 | 
				
			||||||
  <g transform="translate(16,-2)" filter="url(#blur)">
 | 
					 | 
				
			||||||
    <g transform="translate(0,16)">
 | 
					 | 
				
			||||||
      <g class="am-weather-sun" transform="translate(0,16)">
 | 
					 | 
				
			||||||
        <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffc04a" stroke-linecap="round" stroke-width="2" />
 | 
					 | 
				
			||||||
        <g transform="rotate(45)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffc04a" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="rotate(90)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffc04a" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="rotate(135)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffc04a" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="scale(-1)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffc04a" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />F
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="rotate(225)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffc04a" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="rotate(-90)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffc04a" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="rotate(-45)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffc04a" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <circle r="5" fill="#ffc04a" stroke="#ffc04a" stroke-width="2" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
    <g transform="translate(-16,4)">
 | 
					 | 
				
			||||||
      <g class="am-weather-frost" stroke="#57a0ee" transform="translate(0,2)" fill="none" stroke-width="2"
 | 
					 | 
				
			||||||
        stroke-linecap="round"
 | 
					 | 
				
			||||||
        style="-moz-animation-duration:1.11s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-frost;-moz-animation-timing-function:linear;-webkit-animation-duration:1.11s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-frost;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
        <path d="M11,32H45" />
 | 
					 | 
				
			||||||
        <path d="M15.5,37H40.5" />
 | 
					 | 
				
			||||||
        <path d="M22.5,42H33.5" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
      <g>
 | 
					 | 
				
			||||||
        <path stroke="#57a0ee" transform="translate(0,0)" fill="none" stroke-width="2" stroke-linecap="round"
 | 
					 | 
				
			||||||
          d="M28,31V9M28,22l11,-3.67M34,20l2,-4M34,20l4,2M28,22l-11,-3.67M22,20l-2,-4M22,20l-4,2M28,14.27l3.01,-3.02M28,14.27l-3.01,-3.02" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
  </g>
 | 
					 | 
				
			||||||
</svg>
 | 
					 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 7.3 KiB  | 
@@ -1,269 +0,0 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
					 | 
				
			||||||
<!-- (c) ammap.com | SVG weather icons -->
 | 
					 | 
				
			||||||
<svg width="56" height="48" version="1.1" xmlns="http://www.w3.org/2000/svg">
 | 
					 | 
				
			||||||
  <defs>
 | 
					 | 
				
			||||||
    <filter id="blur" x="-.20655" y="-.21122" width="1.403" height="1.4997">
 | 
					 | 
				
			||||||
      <feGaussianBlur in="SourceAlpha" stdDeviation="3" />
 | 
					 | 
				
			||||||
      <feOffset dx="0" dy="4" result="offsetblur" />
 | 
					 | 
				
			||||||
      <feComponentTransfer>
 | 
					 | 
				
			||||||
        <feFuncA slope="0.05" type="linear" />
 | 
					 | 
				
			||||||
      </feComponentTransfer>
 | 
					 | 
				
			||||||
      <feMerge>
 | 
					 | 
				
			||||||
        <feMergeNode />
 | 
					 | 
				
			||||||
        <feMergeNode in="SourceGraphic" />
 | 
					 | 
				
			||||||
      </feMerge>
 | 
					 | 
				
			||||||
    </filter>
 | 
					 | 
				
			||||||
    <style type="text/css">
 | 
					 | 
				
			||||||
      <![CDATA[
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** MOON
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-moon {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          -webkit-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -moz-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -ms-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          transform: rotate(0deg);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        50% {
 | 
					 | 
				
			||||||
          -webkit-transform: rotate(15deg);
 | 
					 | 
				
			||||||
          -moz-transform: rotate(15deg);
 | 
					 | 
				
			||||||
          -ms-transform: rotate(15deg);
 | 
					 | 
				
			||||||
          transform: rotate(15deg);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          -webkit-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -moz-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -ms-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          transform: rotate(0deg);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-moon {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-moon;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-moon;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-moon;
 | 
					 | 
				
			||||||
        animation-name: am-weather-moon;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 6s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 6s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 6s;
 | 
					 | 
				
			||||||
        animation-duration: 6s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -webkit-transform-origin: 12.5px 15.15px 0;
 | 
					 | 
				
			||||||
        /* TODO FF CENTER ISSUE */
 | 
					 | 
				
			||||||
        -moz-transform-origin: 12.5px 15.15px 0;
 | 
					 | 
				
			||||||
        /* TODO FF CENTER ISSUE */
 | 
					 | 
				
			||||||
        -ms-transform-origin: 12.5px 15.15px 0;
 | 
					 | 
				
			||||||
        /* TODO FF CENTER ISSUE */
 | 
					 | 
				
			||||||
        transform-origin: 12.5px 15.15px 0;
 | 
					 | 
				
			||||||
        /* TODO FF CENTER ISSUE */
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      @keyframes am-weather-moon-star-1 {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          opacity: 0;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          opacity: 1;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-moon-star-1 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-moon-star-1;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-moon-star-1;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-moon-star-1;
 | 
					 | 
				
			||||||
        animation-name: am-weather-moon-star-1;
 | 
					 | 
				
			||||||
        -webkit-animation-delay: 3s;
 | 
					 | 
				
			||||||
        -moz-animation-delay: 3s;
 | 
					 | 
				
			||||||
        -ms-animation-delay: 3s;
 | 
					 | 
				
			||||||
        animation-delay: 3s;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 5s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 5s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 5s;
 | 
					 | 
				
			||||||
        animation-duration: 5s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        animation-iteration-count: 1;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      @keyframes am-weather-moon-star-2 {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          opacity: 0;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          opacity: 1;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-moon-star-2 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-moon-star-2;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-moon-star-2;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-moon-star-2;
 | 
					 | 
				
			||||||
        animation-name: am-weather-moon-star-2;
 | 
					 | 
				
			||||||
        -webkit-animation-delay: 5s;
 | 
					 | 
				
			||||||
        -moz-animation-delay: 5s;
 | 
					 | 
				
			||||||
        -ms-animation-delay: 5s;
 | 
					 | 
				
			||||||
        animation-delay: 5s;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 4s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 4s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 4s;
 | 
					 | 
				
			||||||
        animation-duration: 4s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        animation-iteration-count: 1;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** FROST
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-frost {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        1% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        3% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(-0.3px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(-0.3px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(-0.3px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(-0.3px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        5% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        7% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(-0.3px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(-0.3px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(-0.3px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(-0.3px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        9% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        11% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(-0.3px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(-0.3px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(-0.3px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(-0.3px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        13% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        15% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(-0.3px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(-0.3px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(-0.3px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(-0.3px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        16% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-frost {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-frost;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-frost;
 | 
					 | 
				
			||||||
        animation-name: am-weather-frost;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 1.11s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 1.11s;
 | 
					 | 
				
			||||||
        animation-duration: 1.11s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      ]]>
 | 
					 | 
				
			||||||
    </style>
 | 
					 | 
				
			||||||
  </defs>
 | 
					 | 
				
			||||||
  <g transform="translate(16,-2)" filter="url(#blur)">
 | 
					 | 
				
			||||||
    <g transform="matrix(.8 0 0 .8 16 4)">
 | 
					 | 
				
			||||||
      <g class="am-weather-moon-star-1"
 | 
					 | 
				
			||||||
        style="-moz-animation-delay:3s;-moz-animation-duration:5s;-moz-animation-iteration-count:1;-moz-animation-name:am-weather-moon-star-1;-moz-animation-timing-function:linear;-ms-animation-delay:3s;-ms-animation-duration:5s;-ms-animation-iteration-count:1;-ms-animation-name:am-weather-moon-star-1;-ms-animation-timing-function:linear;-webkit-animation-delay:3s;-webkit-animation-duration:5s;-webkit-animation-iteration-count:1;-webkit-animation-name:am-weather-moon-star-1;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
        <polygon points="4 4 3.3 5.2 2.7 4 1.5 3.3 2.7 2.7 3.3 1.5 4 2.7 5.2 3.3" fill="#ffc04a"
 | 
					 | 
				
			||||||
          stroke-miterlimit="10" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
      <g class="am-weather-moon-star-2"
 | 
					 | 
				
			||||||
        style="-moz-animation-delay:5s;-moz-animation-duration:4s;-moz-animation-iteration-count:1;-moz-animation-name:am-weather-moon-star-2;-moz-animation-timing-function:linear;-ms-animation-delay:5s;-ms-animation-duration:4s;-ms-animation-iteration-count:1;-ms-animation-name:am-weather-moon-star-2;-ms-animation-timing-function:linear;-webkit-animation-delay:5s;-webkit-animation-duration:4s;-webkit-animation-iteration-count:1;-webkit-animation-name:am-weather-moon-star-2;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
        <polygon transform="translate(20,10)" points="4 4 3.3 5.2 2.7 4 1.5 3.3 2.7 2.7 3.3 1.5 4 2.7 5.2 3.3"
 | 
					 | 
				
			||||||
          fill="#ffc04a" stroke-miterlimit="10" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
      <g class="am-weather-moon"
 | 
					 | 
				
			||||||
        style="-moz-animation-duration:6s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-moon;-moz-animation-timing-function:linear;-moz-transform-origin:12.5px 15.15px 0;-ms-animation-duration:6s;-ms-animation-iteration-count:infinite;-ms-animation-name:am-weather-moon;-ms-animation-timing-function:linear;-ms-transform-origin:12.5px 15.15px 0;-webkit-animation-duration:6s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-moon;-webkit-animation-timing-function:linear;-webkit-transform-origin:12.5px 15.15px 0">
 | 
					 | 
				
			||||||
        <path
 | 
					 | 
				
			||||||
          d="m14.5 13.2c0-3.7 2-6.9 5-8.7-1.5-0.9-3.2-1.3-5-1.3-5.5 0-10 4.5-10 10s4.5 10 10 10c1.8 0 3.5-0.5 5-1.3-3-1.7-5-5-5-8.7z"
 | 
					 | 
				
			||||||
          fill="#ffc04a" stroke="#ffc04a" stroke-linejoin="round" stroke-width="2" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
    <g transform="translate(-16,4)">
 | 
					 | 
				
			||||||
      <g class="am-weather-frost" stroke="#57a0ee" transform="translate(0,2)" fill="none" stroke-width="2"
 | 
					 | 
				
			||||||
        stroke-linecap="round"
 | 
					 | 
				
			||||||
        style="-moz-animation-duration:1.11s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-frost;-moz-animation-timing-function:linear;-webkit-animation-duration:1.11s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-frost;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
        <path d="M11,32H45" />
 | 
					 | 
				
			||||||
        <path d="M15.5,37H40.5" />
 | 
					 | 
				
			||||||
        <path d="M22.5,42H33.5" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
      <g>
 | 
					 | 
				
			||||||
        <path stroke="#57a0ee" transform="translate(0,0)" fill="none" stroke-width="2" stroke-linecap="round"
 | 
					 | 
				
			||||||
          d="M28,31V9M28,22l11,-3.67M34,20l2,-4M34,20l4,2M28,22l-11,-3.67M22,20l-2,-4M22,20l-4,2M28,14.27l3.01,-3.02M28,14.27l-3.01,-3.02" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
  </g>
 | 
					 | 
				
			||||||
</svg>
 | 
					 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 11 KiB  | 
@@ -1,141 +0,0 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
					 | 
				
			||||||
<!-- (c) ammap.com | SVG weather icons -->
 | 
					 | 
				
			||||||
<!-- Mix of Rain and Sleet | Contributed by hsoJ95 on GitHub: https://github.com/hsoj95 -->
 | 
					 | 
				
			||||||
<svg width="56" height="48" version="1.1" xmlns="http://www.w3.org/2000/svg">
 | 
					 | 
				
			||||||
  <defs>
 | 
					 | 
				
			||||||
    <filter id="blur" x="-.24684" y="-.22776" width="1.4937" height="1.5756">
 | 
					 | 
				
			||||||
      <feGaussianBlur in="SourceAlpha" stdDeviation="3" />
 | 
					 | 
				
			||||||
      <feOffset dx="0" dy="4" result="offsetblur" />
 | 
					 | 
				
			||||||
      <feComponentTransfer>
 | 
					 | 
				
			||||||
        <feFuncA slope="0.05" type="linear" />
 | 
					 | 
				
			||||||
      </feComponentTransfer>
 | 
					 | 
				
			||||||
      <feMerge>
 | 
					 | 
				
			||||||
        <feMergeNode />
 | 
					 | 
				
			||||||
        <feMergeNode in="SourceGraphic" />
 | 
					 | 
				
			||||||
      </feMerge>
 | 
					 | 
				
			||||||
    </filter>
 | 
					 | 
				
			||||||
    <style type="text/css">
 | 
					 | 
				
			||||||
      <![CDATA[
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** RAIN
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-rain {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          stroke-dashoffset: 0;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          stroke-dashoffset: -100;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-rain-1 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-rain;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-rain;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-rain;
 | 
					 | 
				
			||||||
        animation-name: am-weather-rain;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 8s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 8s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 8s;
 | 
					 | 
				
			||||||
        animation-duration: 8s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-rain-2 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-rain;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-rain;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-rain;
 | 
					 | 
				
			||||||
        animation-name: am-weather-rain;
 | 
					 | 
				
			||||||
        -webkit-animation-delay: 0.25s;
 | 
					 | 
				
			||||||
        -moz-animation-delay: 0.25s;
 | 
					 | 
				
			||||||
        -ms-animation-delay: 0.25s;
 | 
					 | 
				
			||||||
        animation-delay: 0.25s;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 8s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 8s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 8s;
 | 
					 | 
				
			||||||
        animation-duration: 8s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** CLOUDS
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-cloud-3 {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        50% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-cloud-3 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-cloud-3;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-cloud-3;
 | 
					 | 
				
			||||||
        animation-name: am-weather-cloud-3;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 3s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 3s;
 | 
					 | 
				
			||||||
        animation-duration: 3s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      ]]>
 | 
					 | 
				
			||||||
    </style>
 | 
					 | 
				
			||||||
  </defs>
 | 
					 | 
				
			||||||
  <g transform="translate(16,-2)" filter="url(#blur)">
 | 
					 | 
				
			||||||
    <g class="am-weather-cloud-3"
 | 
					 | 
				
			||||||
      style="-moz-animation-duration:3s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-cloud-3;-moz-animation-timing-function:linear;-webkit-animation-duration:3s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-cloud-3;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
      <path transform="translate(-20,-11)"
 | 
					 | 
				
			||||||
        d="m47.7 35.4c0-4.6-3.7-8.2-8.2-8.2-1 0-1.9 0.2-2.8 0.5-0.3-3.4-3.1-6.2-6.6-6.2-3.7 0-6.7 3-6.7 6.7 0 0.8 0.2 1.6 0.4 2.3-0.3-0.1-0.7-0.1-1-0.1-3.7 0-6.7 3-6.7 6.7 0 3.6 2.9 6.6 6.5 6.7h17.2c4.4-0.5 7.9-4 7.9-8.4z"
 | 
					 | 
				
			||||||
        fill="#57a0ee" stroke="#fff" stroke-linejoin="round" stroke-width="1.2" />
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
    <g class="am-weather-sleet-2" transform="translate(-20,-10) rotate(10,-247.39,200.17)" fill="none" stroke="#91c0f8"
 | 
					 | 
				
			||||||
      stroke-linecap="round">
 | 
					 | 
				
			||||||
      <line class="am-weather-rain-1" transform="translate(-5,1)" y2="8" stroke-dasharray="0.1, 7" stroke-width="2"
 | 
					 | 
				
			||||||
        style="-moz-animation-duration:8s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-rain;-moz-animation-timing-function:linear;-ms-animation-duration:8s;-ms-animation-iteration-count:infinite;-ms-animation-name:am-weather-rain;-ms-animation-timing-function:linear;-webkit-animation-duration:8s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-rain;-webkit-animation-timing-function:linear" />
 | 
					 | 
				
			||||||
      <line class="am-weather-rain-1" transform="translate(5)" y2="8" stroke-dasharray="0.1, 7" stroke-width="2"
 | 
					 | 
				
			||||||
        style="-moz-animation-duration:8s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-rain;-moz-animation-timing-function:linear;-ms-animation-duration:8s;-ms-animation-iteration-count:infinite;-ms-animation-name:am-weather-rain;-ms-animation-timing-function:linear;-webkit-animation-duration:8s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-rain;-webkit-animation-timing-function:linear" />
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
    <g class="am-weather-rain-3" transform="translate(-20,-10) rotate(10,-245.89,217.31)" fill="none" stroke="#91c0f8"
 | 
					 | 
				
			||||||
      stroke-dasharray="4, 7" stroke-linecap="round" stroke-width="2">
 | 
					 | 
				
			||||||
      <line class="am-weather-rain-1" transform="translate(-13,1)" y2="8"
 | 
					 | 
				
			||||||
        style="-moz-animation-duration:8s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-rain;-moz-animation-timing-function:linear;-ms-animation-duration:8s;-ms-animation-iteration-count:infinite;-ms-animation-name:am-weather-rain;-ms-animation-timing-function:linear;-webkit-animation-duration:8s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-rain;-webkit-animation-timing-function:linear" />
 | 
					 | 
				
			||||||
      <line class="am-weather-rain-1" transform="translate(-3,2)" y2="8"
 | 
					 | 
				
			||||||
        style="-moz-animation-duration:8s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-rain;-moz-animation-timing-function:linear;-ms-animation-duration:8s;-ms-animation-iteration-count:infinite;-ms-animation-name:am-weather-rain;-ms-animation-timing-function:linear;-webkit-animation-duration:8s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-rain;-webkit-animation-timing-function:linear" />
 | 
					 | 
				
			||||||
      <line class="am-weather-rain-2" transform="translate(7,-1)" y2="8"
 | 
					 | 
				
			||||||
        style="-moz-animation-delay:0.25s;-moz-animation-duration:8s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-rain;-moz-animation-timing-function:linear;-ms-animation-delay:0.25s;-ms-animation-duration:8s;-ms-animation-iteration-count:infinite;-ms-animation-name:am-weather-rain;-ms-animation-timing-function:linear;-webkit-animation-delay:0.25s;-webkit-animation-duration:8s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-rain;-webkit-animation-timing-function:linear" />
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
  </g>
 | 
					 | 
				
			||||||
</svg>
 | 
					 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 7.6 KiB  | 
@@ -1,179 +0,0 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
					 | 
				
			||||||
<!-- (c) ammap.com | SVG weather icons -->
 | 
					 | 
				
			||||||
<svg width="56" height="48" version="1.1" xmlns="http://www.w3.org/2000/svg">
 | 
					 | 
				
			||||||
  <defs>
 | 
					 | 
				
			||||||
    <filter id="blur" x="-.20655" y="-.21122" width="1.403" height="1.4997">
 | 
					 | 
				
			||||||
      <feGaussianBlur in="SourceAlpha" stdDeviation="3" />
 | 
					 | 
				
			||||||
      <feOffset dx="0" dy="4" result="offsetblur" />
 | 
					 | 
				
			||||||
      <feComponentTransfer>
 | 
					 | 
				
			||||||
        <feFuncA slope="0.05" type="linear" />
 | 
					 | 
				
			||||||
      </feComponentTransfer>
 | 
					 | 
				
			||||||
      <feMerge>
 | 
					 | 
				
			||||||
        <feMergeNode />
 | 
					 | 
				
			||||||
        <feMergeNode in="SourceGraphic" />
 | 
					 | 
				
			||||||
      </feMerge>
 | 
					 | 
				
			||||||
    </filter>
 | 
					 | 
				
			||||||
    <style type="text/css">
 | 
					 | 
				
			||||||
      <![CDATA[
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** SUN
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-sun {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          -webkit-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -moz-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -ms-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          transform: rotate(0deg);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          -webkit-transform: rotate(360deg);
 | 
					 | 
				
			||||||
          -moz-transform: rotate(360deg);
 | 
					 | 
				
			||||||
          -ms-transform: rotate(360deg);
 | 
					 | 
				
			||||||
          transform: rotate(360deg);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-sun {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-sun;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-sun;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-sun;
 | 
					 | 
				
			||||||
        animation-name: am-weather-sun;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 9s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 9s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 9s;
 | 
					 | 
				
			||||||
        animation-duration: 9s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** CLOUDS
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-cloud-2 {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        50% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-cloud-2 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-cloud-2;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-cloud-2;
 | 
					 | 
				
			||||||
        animation-name: am-weather-cloud-2;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 3s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 3s;
 | 
					 | 
				
			||||||
        animation-duration: 3s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** RAIN
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-rain {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          stroke-dashoffset: 0;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          stroke-dashoffset: -100;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-rain-1 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-rain;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-rain;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-rain;
 | 
					 | 
				
			||||||
        animation-name: am-weather-rain;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 8s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 8s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 8s;
 | 
					 | 
				
			||||||
        animation-duration: 8s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      ]]>
 | 
					 | 
				
			||||||
    </style>
 | 
					 | 
				
			||||||
  </defs>
 | 
					 | 
				
			||||||
  <g transform="translate(16,-2)" filter="url(#blur)">
 | 
					 | 
				
			||||||
    <g transform="translate(0,16)">
 | 
					 | 
				
			||||||
      <g class="am-weather-sun"
 | 
					 | 
				
			||||||
        style="-moz-animation-duration:9s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-sun;-moz-animation-timing-function:linear;-ms-animation-duration:9s;-ms-animation-iteration-count:infinite;-ms-animation-name:am-weather-sun;-ms-animation-timing-function:linear;-webkit-animation-duration:9s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-sun;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
        <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round" stroke-width="2" />
 | 
					 | 
				
			||||||
        <g transform="rotate(45)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="rotate(90)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="rotate(135)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="scale(-1)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="rotate(225)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="rotate(-90)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="rotate(-45)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <circle r="5" fill="#ffa500" stroke="#ffa500" stroke-width="2" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
    <g class="am-weather-cloud-3"
 | 
					 | 
				
			||||||
      style="-moz-animation-duration:3s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-cloud-2;-moz-animation-timing-function:linear;-webkit-animation-duration:3s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-cloud-2;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
      <path transform="translate(-20,-11)"
 | 
					 | 
				
			||||||
        d="m47.7 35.4c0-4.6-3.7-8.2-8.2-8.2-1 0-1.9 0.2-2.8 0.5-0.3-3.4-3.1-6.2-6.6-6.2-3.7 0-6.7 3-6.7 6.7 0 0.8 0.2 1.6 0.4 2.3-0.3-0.1-0.7-0.1-1-0.1-3.7 0-6.7 3-6.7 6.7 0 3.6 2.9 6.6 6.5 6.7h17.2c4.4-0.5 7.9-4 7.9-8.4z"
 | 
					 | 
				
			||||||
        fill="#57a0ee" stroke="#fff" stroke-linejoin="round" stroke-width="1.2" />
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
    <g class="am-weather-rain-1" transform="translate(-20,-10) rotate(10,-238.68,233.96)">
 | 
					 | 
				
			||||||
      <line class="am-weather-rain-1" transform="translate(-6,1)" y2="8" fill="none" stroke="#91c0f8"
 | 
					 | 
				
			||||||
        stroke-dasharray="4, 7" stroke-linecap="round" stroke-width="2"
 | 
					 | 
				
			||||||
        style="-moz-animation-duration:8s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-rain;-moz-animation-timing-function:linear;-ms-animation-duration:8s;-ms-animation-iteration-count:infinite;-ms-animation-name:am-weather-rain;-ms-animation-timing-function:linear;-webkit-animation-duration:8s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-rain;-webkit-animation-timing-function:linear" />
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
  </g>
 | 
					 | 
				
			||||||
</svg>
 | 
					 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 7.4 KiB  | 
@@ -1,243 +0,0 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
					 | 
				
			||||||
<!-- (c) ammap.com | SVG weather icons -->
 | 
					 | 
				
			||||||
<svg width="56" height="48" version="1.1" xmlns="http://www.w3.org/2000/svg">
 | 
					 | 
				
			||||||
  <defs>
 | 
					 | 
				
			||||||
    <filter id="blur" x="-.20655" y="-.21122" width="1.403" height="1.4997">
 | 
					 | 
				
			||||||
      <feGaussianBlur in="SourceAlpha" stdDeviation="3" />
 | 
					 | 
				
			||||||
      <feOffset dx="0" dy="4" result="offsetblur" />
 | 
					 | 
				
			||||||
      <feComponentTransfer>
 | 
					 | 
				
			||||||
        <feFuncA slope="0.05" type="linear" />
 | 
					 | 
				
			||||||
      </feComponentTransfer>
 | 
					 | 
				
			||||||
      <feMerge>
 | 
					 | 
				
			||||||
        <feMergeNode />
 | 
					 | 
				
			||||||
        <feMergeNode in="SourceGraphic" />
 | 
					 | 
				
			||||||
      </feMerge>
 | 
					 | 
				
			||||||
    </filter>
 | 
					 | 
				
			||||||
    <style type="text/css">
 | 
					 | 
				
			||||||
      <![CDATA[
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** MOON
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-moon {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          -webkit-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -moz-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -ms-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          transform: rotate(0deg);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        50% {
 | 
					 | 
				
			||||||
          -webkit-transform: rotate(15deg);
 | 
					 | 
				
			||||||
          -moz-transform: rotate(15deg);
 | 
					 | 
				
			||||||
          -ms-transform: rotate(15deg);
 | 
					 | 
				
			||||||
          transform: rotate(15deg);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          -webkit-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -moz-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -ms-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          transform: rotate(0deg);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-moon {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-moon;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-moon;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-moon;
 | 
					 | 
				
			||||||
        animation-name: am-weather-moon;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 6s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 6s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 6s;
 | 
					 | 
				
			||||||
        animation-duration: 6s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -webkit-transform-origin: 12.5px 15.15px 0;
 | 
					 | 
				
			||||||
        /* TODO FF CENTER ISSUE */
 | 
					 | 
				
			||||||
        -moz-transform-origin: 12.5px 15.15px 0;
 | 
					 | 
				
			||||||
        /* TODO FF CENTER ISSUE */
 | 
					 | 
				
			||||||
        -ms-transform-origin: 12.5px 15.15px 0;
 | 
					 | 
				
			||||||
        /* TODO FF CENTER ISSUE */
 | 
					 | 
				
			||||||
        transform-origin: 12.5px 15.15px 0;
 | 
					 | 
				
			||||||
        /* TODO FF CENTER ISSUE */
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      @keyframes am-weather-moon-star-1 {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          opacity: 0;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          opacity: 1;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-moon-star-1 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-moon-star-1;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-moon-star-1;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-moon-star-1;
 | 
					 | 
				
			||||||
        animation-name: am-weather-moon-star-1;
 | 
					 | 
				
			||||||
        -webkit-animation-delay: 3s;
 | 
					 | 
				
			||||||
        -moz-animation-delay: 3s;
 | 
					 | 
				
			||||||
        -ms-animation-delay: 3s;
 | 
					 | 
				
			||||||
        animation-delay: 3s;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 5s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 5s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 5s;
 | 
					 | 
				
			||||||
        animation-duration: 5s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        animation-iteration-count: 1;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      @keyframes am-weather-moon-star-2 {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          opacity: 0;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          opacity: 1;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-moon-star-2 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-moon-star-2;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-moon-star-2;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-moon-star-2;
 | 
					 | 
				
			||||||
        animation-name: am-weather-moon-star-2;
 | 
					 | 
				
			||||||
        -webkit-animation-delay: 5s;
 | 
					 | 
				
			||||||
        -moz-animation-delay: 5s;
 | 
					 | 
				
			||||||
        -ms-animation-delay: 5s;
 | 
					 | 
				
			||||||
        animation-delay: 5s;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 4s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 4s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 4s;
 | 
					 | 
				
			||||||
        animation-duration: 4s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        animation-iteration-count: 1;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** CLOUDS
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-cloud-2 {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        50% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-cloud-2 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-cloud-2;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-cloud-2;
 | 
					 | 
				
			||||||
        animation-name: am-weather-cloud-2;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 3s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 3s;
 | 
					 | 
				
			||||||
        animation-duration: 3s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** RAIN
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-rain {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          stroke-dashoffset: 0;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          stroke-dashoffset: -100;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-rain-1 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-rain;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-rain;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-rain;
 | 
					 | 
				
			||||||
        animation-name: am-weather-rain;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 8s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 8s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 8s;
 | 
					 | 
				
			||||||
        animation-duration: 8s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      ]]>
 | 
					 | 
				
			||||||
    </style>
 | 
					 | 
				
			||||||
  </defs>
 | 
					 | 
				
			||||||
  <g transform="translate(16,-2)" filter="url(#blur)">
 | 
					 | 
				
			||||||
    <g transform="matrix(.8 0 0 .8 16 4)">
 | 
					 | 
				
			||||||
      <g class="am-weather-moon-star-1"
 | 
					 | 
				
			||||||
        style="-moz-animation-delay:3s;-moz-animation-duration:5s;-moz-animation-iteration-count:1;-moz-animation-name:am-weather-moon-star-1;-moz-animation-timing-function:linear;-ms-animation-delay:3s;-ms-animation-duration:5s;-ms-animation-iteration-count:1;-ms-animation-name:am-weather-moon-star-1;-ms-animation-timing-function:linear;-webkit-animation-delay:3s;-webkit-animation-duration:5s;-webkit-animation-iteration-count:1;-webkit-animation-name:am-weather-moon-star-1;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
        <polygon points="4 4 3.3 5.2 2.7 4 1.5 3.3 2.7 2.7 3.3 1.5 4 2.7 5.2 3.3" fill="#ffa500"
 | 
					 | 
				
			||||||
          stroke-miterlimit="10" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
      <g class="am-weather-moon-star-2"
 | 
					 | 
				
			||||||
        style="-moz-animation-delay:5s;-moz-animation-duration:4s;-moz-animation-iteration-count:1;-moz-animation-name:am-weather-moon-star-2;-moz-animation-timing-function:linear;-ms-animation-delay:5s;-ms-animation-duration:4s;-ms-animation-iteration-count:1;-ms-animation-name:am-weather-moon-star-2;-ms-animation-timing-function:linear;-webkit-animation-delay:5s;-webkit-animation-duration:4s;-webkit-animation-iteration-count:1;-webkit-animation-name:am-weather-moon-star-2;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
        <polygon transform="translate(20,10)" points="4 4 3.3 5.2 2.7 4 1.5 3.3 2.7 2.7 3.3 1.5 4 2.7 5.2 3.3"
 | 
					 | 
				
			||||||
          fill="#ffa500" stroke-miterlimit="10" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
      <g class="am-weather-moon"
 | 
					 | 
				
			||||||
        style="-moz-animation-duration:6s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-moon;-moz-animation-timing-function:linear;-moz-transform-origin:12.5px 15.15px 0;-ms-animation-duration:6s;-ms-animation-iteration-count:infinite;-ms-animation-name:am-weather-moon;-ms-animation-timing-function:linear;-ms-transform-origin:12.5px 15.15px 0;-webkit-animation-duration:6s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-moon;-webkit-animation-timing-function:linear;-webkit-transform-origin:12.5px 15.15px 0">
 | 
					 | 
				
			||||||
        <path
 | 
					 | 
				
			||||||
          d="m14.5 13.2c0-3.7 2-6.9 5-8.7-1.5-0.9-3.2-1.3-5-1.3-5.5 0-10 4.5-10 10s4.5 10 10 10c1.8 0 3.5-0.5 5-1.3-3-1.7-5-5-5-8.7z"
 | 
					 | 
				
			||||||
          fill="#ffa500" stroke="#ffa500" stroke-linejoin="round" stroke-width="2" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
    <g class="am-weather-cloud-3"
 | 
					 | 
				
			||||||
      style="-moz-animation-duration:3s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-cloud-2;-moz-animation-timing-function:linear;-webkit-animation-duration:3s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-cloud-2;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
      <path transform="translate(-20,-11)"
 | 
					 | 
				
			||||||
        d="m47.7 35.4c0-4.6-3.7-8.2-8.2-8.2-1 0-1.9 0.2-2.8 0.5-0.3-3.4-3.1-6.2-6.6-6.2-3.7 0-6.7 3-6.7 6.7 0 0.8 0.2 1.6 0.4 2.3-0.3-0.1-0.7-0.1-1-0.1-3.7 0-6.7 3-6.7 6.7 0 3.6 2.9 6.6 6.5 6.7h17.2c4.4-0.5 7.9-4 7.9-8.4z"
 | 
					 | 
				
			||||||
        fill="#57a0ee" stroke="#fff" stroke-linejoin="round" stroke-width="1.2" />
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
    <g class="am-weaher-rain-1" transform="translate(-20,-10) rotate(10,-238.68,233.96)">
 | 
					 | 
				
			||||||
      <line class="am-weather-rain-1" transform="translate(-6,1)" y2="8" fill="none" stroke="#91c0f8"
 | 
					 | 
				
			||||||
        stroke-dasharray="4, 7" stroke-linecap="round" stroke-width="2"
 | 
					 | 
				
			||||||
        style="-moz-animation-duration:8s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-rain;-moz-animation-timing-function:linear;-ms-animation-duration:8s;-ms-animation-iteration-count:infinite;-ms-animation-name:am-weather-rain;-ms-animation-timing-function:linear;-webkit-animation-duration:8s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-rain;-webkit-animation-timing-function:linear" />
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
  </g>
 | 
					 | 
				
			||||||
</svg>
 | 
					 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 10 KiB  | 
@@ -1,204 +0,0 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
					 | 
				
			||||||
<!-- (c) ammap.com | SVG weather icons -->
 | 
					 | 
				
			||||||
<svg width="56" height="48" version="1.1" xmlns="http://www.w3.org/2000/svg">
 | 
					 | 
				
			||||||
  <defs>
 | 
					 | 
				
			||||||
    <filter id="blur" x="-.20655" y="-.20592" width="1.403" height="1.4872">
 | 
					 | 
				
			||||||
      <feGaussianBlur in="SourceAlpha" stdDeviation="3" />
 | 
					 | 
				
			||||||
      <feOffset dx="0" dy="4" result="offsetblur" />
 | 
					 | 
				
			||||||
      <feComponentTransfer>
 | 
					 | 
				
			||||||
        <feFuncA slope="0.05" type="linear" />
 | 
					 | 
				
			||||||
      </feComponentTransfer>
 | 
					 | 
				
			||||||
      <feMerge>
 | 
					 | 
				
			||||||
        <feMergeNode />
 | 
					 | 
				
			||||||
        <feMergeNode in="SourceGraphic" />
 | 
					 | 
				
			||||||
      </feMerge>
 | 
					 | 
				
			||||||
    </filter>
 | 
					 | 
				
			||||||
    <style type="text/css">
 | 
					 | 
				
			||||||
      <![CDATA[
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** SUN
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-sun {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          -webkit-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -moz-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -ms-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          transform: rotate(0deg);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          -webkit-transform: rotate(360deg);
 | 
					 | 
				
			||||||
          -moz-transform: rotate(360deg);
 | 
					 | 
				
			||||||
          -ms-transform: rotate(360deg);
 | 
					 | 
				
			||||||
          transform: rotate(360deg);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-sun {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-sun;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-sun;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-sun;
 | 
					 | 
				
			||||||
        animation-name: am-weather-sun;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 9s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 9s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 9s;
 | 
					 | 
				
			||||||
        animation-duration: 9s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** CLOUDS
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-cloud-2 {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        50% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-cloud-2 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-cloud-2;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-cloud-2;
 | 
					 | 
				
			||||||
        animation-name: am-weather-cloud-2;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 3s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 3s;
 | 
					 | 
				
			||||||
        animation-duration: 3s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** RAIN
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-rain {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          stroke-dashoffset: 0;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          stroke-dashoffset: -100;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-rain-1 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-rain;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-rain;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-rain;
 | 
					 | 
				
			||||||
        animation-name: am-weather-rain;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 8s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 8s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 8s;
 | 
					 | 
				
			||||||
        animation-duration: 8s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-rain-2 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-rain;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-rain;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-rain;
 | 
					 | 
				
			||||||
        animation-name: am-weather-rain;
 | 
					 | 
				
			||||||
        -webkit-animation-delay: 0.25s;
 | 
					 | 
				
			||||||
        -moz-animation-delay: 0.25s;
 | 
					 | 
				
			||||||
        -ms-animation-delay: 0.25s;
 | 
					 | 
				
			||||||
        animation-delay: 0.25s;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 8s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 8s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 8s;
 | 
					 | 
				
			||||||
        animation-duration: 8s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      ]]>
 | 
					 | 
				
			||||||
    </style>
 | 
					 | 
				
			||||||
  </defs>
 | 
					 | 
				
			||||||
  <g transform="translate(16,-2)" filter="url(#blur)">
 | 
					 | 
				
			||||||
    <g transform="translate(0,16)">
 | 
					 | 
				
			||||||
      <g class="am-weather-sun"
 | 
					 | 
				
			||||||
        style="-moz-animation-duration:9s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-sun;-moz-animation-timing-function:linear;-ms-animation-duration:9s;-ms-animation-iteration-count:infinite;-ms-animation-name:am-weather-sun;-ms-animation-timing-function:linear;-webkit-animation-duration:9s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-sun;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
        <line transform="translate(0,9)" y2="3" stroke="#ffa500" stroke-linecap="round" stroke-width="2" fifll="none" />
 | 
					 | 
				
			||||||
        <g transform="rotate(45)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="rotate(90)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="rotate(135)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="scale(-1)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="rotate(225)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="rotate(-90)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="rotate(-45)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <circle r="5" fill="#ffa500" stroke="#ffa500" stroke-width="2" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
    <g class="am-weather-cloud-3"
 | 
					 | 
				
			||||||
      style="-moz-animation-duration:3s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-cloud-2;-moz-animation-timing-function:linear;-webkit-animation-duration:3s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-cloud-2;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
      <path transform="translate(-20,-11)"
 | 
					 | 
				
			||||||
        d="m47.7 35.4c0-4.6-3.7-8.2-8.2-8.2-1 0-1.9 0.2-2.8 0.5-0.3-3.4-3.1-6.2-6.6-6.2-3.7 0-6.7 3-6.7 6.7 0 0.8 0.2 1.6 0.4 2.3-0.3-0.1-0.7-0.1-1-0.1-3.7 0-6.7 3-6.7 6.7 0 3.6 2.9 6.6 6.5 6.7h17.2c4.4-0.5 7.9-4 7.9-8.4z"
 | 
					 | 
				
			||||||
        fill="#57a0ee" stroke="#fff" stroke-linejoin="round" stroke-width="1.2" />
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
    <g transform="translate(-20,-10) rotate(10,-245.89,217.31)" fill="none" stroke="#91c0f8" stroke-dasharray="4, 7" stroke-linecap="round"
 | 
					 | 
				
			||||||
      stroke-width="2">
 | 
					 | 
				
			||||||
      <line class="am-weather-rain-1" transform="translate(-6,1)" y2="8"
 | 
					 | 
				
			||||||
        style="-moz-animation-duration:8s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-rain;-moz-animation-timing-function:linear;-ms-animation-duration:8s;-ms-animation-iteration-count:infinite;-ms-animation-name:am-weather-rain;-ms-animation-timing-function:linear;-webkit-animation-duration:8s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-rain;-webkit-animation-timing-function:linear" />
 | 
					 | 
				
			||||||
      <line class="am-weather-rain-2" transform="translate(0,-1)" y2="8"
 | 
					 | 
				
			||||||
        style="-moz-animation-delay:0.25s;-moz-animation-duration:8s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-rain;-moz-animation-timing-function:linear;-ms-animation-delay:0.25s;-ms-animation-duration:8s;-ms-animation-iteration-count:infinite;-ms-animation-name:am-weather-rain;-ms-animation-timing-function:linear;-webkit-animation-delay:0.25s;-webkit-animation-duration:8s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-rain;-webkit-animation-timing-function:linear" />
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
  </g>
 | 
					 | 
				
			||||||
</svg>
 | 
					 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 8.8 KiB  | 
@@ -1,256 +0,0 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
					 | 
				
			||||||
<svg width="56" height="48" version="1.1" xmlns="http://www.w3.org/2000/svg">
 | 
					 | 
				
			||||||
  <defs>
 | 
					 | 
				
			||||||
    <style type="text/css">
 | 
					 | 
				
			||||||
      <![CDATA[
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** CLOUDS
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-cloud-2 {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        50% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-cloud-2 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-cloud-2;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-cloud-2;
 | 
					 | 
				
			||||||
        animation-name: am-weather-cloud-2;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 3s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 3s;
 | 
					 | 
				
			||||||
        animation-duration: 3s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** MOON
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-moon {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          -webkit-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -moz-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -ms-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          transform: rotate(0deg);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        50% {
 | 
					 | 
				
			||||||
          -webkit-transform: rotate(15deg);
 | 
					 | 
				
			||||||
          -moz-transform: rotate(15deg);
 | 
					 | 
				
			||||||
          -ms-transform: rotate(15deg);
 | 
					 | 
				
			||||||
          transform: rotate(15deg);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          -webkit-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -moz-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -ms-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          transform: rotate(0deg);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-moon {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-moon;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-moon;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-moon;
 | 
					 | 
				
			||||||
        animation-name: am-weather-moon;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 6s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 6s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 6s;
 | 
					 | 
				
			||||||
        animation-duration: 6s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -webkit-transform-origin: 12.5px 15.15px 0;
 | 
					 | 
				
			||||||
        /* TODO FF CENTER ISSUE */
 | 
					 | 
				
			||||||
        -moz-transform-origin: 12.5px 15.15px 0;
 | 
					 | 
				
			||||||
        /* TODO FF CENTER ISSUE */
 | 
					 | 
				
			||||||
        -ms-transform-origin: 12.5px 15.15px 0;
 | 
					 | 
				
			||||||
        /* TODO FF CENTER ISSUE */
 | 
					 | 
				
			||||||
        transform-origin: 12.5px 15.15px 0;
 | 
					 | 
				
			||||||
        /* TODO FF CENTER ISSUE */
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      @keyframes am-weather-moon-star-1 {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          opacity: 0;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          opacity: 1;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-moon-star-1 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-moon-star-1;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-moon-star-1;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-moon-star-1;
 | 
					 | 
				
			||||||
        animation-name: am-weather-moon-star-1;
 | 
					 | 
				
			||||||
        -webkit-animation-delay: 3s;
 | 
					 | 
				
			||||||
        -moz-animation-delay: 3s;
 | 
					 | 
				
			||||||
        -ms-animation-delay: 3s;
 | 
					 | 
				
			||||||
        animation-delay: 3s;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 5s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 5s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 5s;
 | 
					 | 
				
			||||||
        animation-duration: 5s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        animation-iteration-count: 1;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      @keyframes am-weather-moon-star-2 {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          opacity: 0;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          opacity: 1;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-moon-star-2 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-moon-star-2;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-moon-star-2;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-moon-star-2;
 | 
					 | 
				
			||||||
        animation-name: am-weather-moon-star-2;
 | 
					 | 
				
			||||||
        -webkit-animation-delay: 5s;
 | 
					 | 
				
			||||||
        -moz-animation-delay: 5s;
 | 
					 | 
				
			||||||
        -ms-animation-delay: 5s;
 | 
					 | 
				
			||||||
        animation-delay: 5s;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 4s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 4s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 4s;
 | 
					 | 
				
			||||||
        animation-duration: 4s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        animation-iteration-count: 1;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** RAIN
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-rain {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          stroke-dashoffset: 0;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          stroke-dashoffset: -100;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-rain-1 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-rain;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-rain;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-rain;
 | 
					 | 
				
			||||||
        animation-name: am-weather-rain;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 8s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 8s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 8s;
 | 
					 | 
				
			||||||
        animation-duration: 8s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-rain-2 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-rain;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-rain;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-rain;
 | 
					 | 
				
			||||||
        animation-name: am-weather-rain;
 | 
					 | 
				
			||||||
        -webkit-animation-delay: 0.25s;
 | 
					 | 
				
			||||||
        -moz-animation-delay: 0.25s;
 | 
					 | 
				
			||||||
        -ms-animation-delay: 0.25s;
 | 
					 | 
				
			||||||
        animation-delay: 0.25s;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 8s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 8s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 8s;
 | 
					 | 
				
			||||||
        animation-duration: 8s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      ]]>
 | 
					 | 
				
			||||||
    </style>
 | 
					 | 
				
			||||||
  </defs>
 | 
					 | 
				
			||||||
  <g class="layer" transform="translate(16,-2)">
 | 
					 | 
				
			||||||
    <g transform="matrix(.8 0 0 .8 16 4)">
 | 
					 | 
				
			||||||
      <g class="am-weather-moon-star-1"
 | 
					 | 
				
			||||||
        style="-moz-animation-delay:3s;-moz-animation-duration:5s;-moz-animation-iteration-count:1;-moz-animation-name:am-weather-moon-star-1;-moz-animation-timing-function:linear;-ms-animation-delay:3s;-ms-animation-duration:5s;-ms-animation-iteration-count:1;-ms-animation-name:am-weather-moon-star-1;-ms-animation-timing-function:linear;-webkit-animation-delay:3s;-webkit-animation-duration:5s;-webkit-animation-iteration-count:1;-webkit-animation-name:am-weather-moon-star-1;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
        <polygon points="4 4 3.3 5.2 2.7 4 1.5 3.3 2.7 2.7 3.3 1.5 4 2.7 5.2 3.3" fill="#ffa500"
 | 
					 | 
				
			||||||
          stroke-miterlimit="10" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
      <g class="am-weather-moon-star-2"
 | 
					 | 
				
			||||||
        style="-moz-animation-delay:5s;-moz-animation-duration:4s;-moz-animation-iteration-count:1;-moz-animation-name:am-weather-moon-star-2;-moz-animation-timing-function:linear;-ms-animation-delay:5s;-ms-animation-duration:4s;-ms-animation-iteration-count:1;-ms-animation-name:am-weather-moon-star-2;-ms-animation-timing-function:linear;-webkit-animation-delay:5s;-webkit-animation-duration:4s;-webkit-animation-iteration-count:1;-webkit-animation-name:am-weather-moon-star-2;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
        <polygon transform="translate(20,10)" points="4 4 3.3 5.2 2.7 4 1.5 3.3 2.7 2.7 3.3 1.5 4 2.7 5.2 3.3"
 | 
					 | 
				
			||||||
          fill="#ffa500" stroke-miterlimit="10" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
      <g class="am-weather-moon"
 | 
					 | 
				
			||||||
        style="-moz-animation-duration:6s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-moon;-moz-animation-timing-function:linear;-moz-transform-origin:12.5px 15.15px 0;-ms-animation-duration:6s;-ms-animation-iteration-count:infinite;-ms-animation-name:am-weather-moon;-ms-animation-timing-function:linear;-ms-transform-origin:12.5px 15.15px 0;-webkit-animation-duration:6s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-moon;-webkit-animation-timing-function:linear;-webkit-transform-origin:12.5px 15.15px 0">
 | 
					 | 
				
			||||||
        <path
 | 
					 | 
				
			||||||
          d="m14.5 13.2c0-3.7 2-6.9 5-8.7-1.5-0.9-3.2-1.3-5-1.3-5.5 0-10 4.5-10 10s4.5 10 10 10c1.8 0 3.5-0.5 5-1.3-3-1.7-5-5-5-8.7z"
 | 
					 | 
				
			||||||
          fill="#ffa500" stroke="#ffa500" stroke-linejoin="round" stroke-width="2" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
    <g class="am-weather-cloud-3"
 | 
					 | 
				
			||||||
      style="-moz-animation-duration:3s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-cloud-2;-moz-animation-timing-function:linear;-webkit-animation-duration:3s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-cloud-2;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
      <path transform="translate(-20,-11)"
 | 
					 | 
				
			||||||
        d="m47.7 35.4c0-4.6-3.7-8.2-8.2-8.2-1 0-1.9 0.2-2.8 0.5-0.3-3.4-3.1-6.2-6.6-6.2-3.7 0-6.7 3-6.7 6.7 0 0.8 0.2 1.6 0.4 2.3-0.3-0.1-0.7-0.1-1-0.1-3.7 0-6.7 3-6.7 6.7 0 3.6 2.9 6.6 6.5 6.7h17.2c4.4-0.5 7.9-4 7.9-8.4z"
 | 
					 | 
				
			||||||
        fill="#57a0ee" stroke="#fff" stroke-linejoin="round" stroke-width="1.2" />
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
    <g class="am-weather-rain-2" transform="translate(-20,-10) rotate(10,34,46)" fill="none" stroke="#91c0f8"
 | 
					 | 
				
			||||||
      stroke-dasharray="4, 7" stroke-linecap="round" stroke-width="2">
 | 
					 | 
				
			||||||
      <line class="am-weather-rain-1" transform="translate(-6,1)" x1="34" x2="34" y1="46" y2="54"
 | 
					 | 
				
			||||||
        style="-moz-animation-duration:8s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-rain;-moz-animation-timing-function:linear;-ms-animation-duration:8s;-ms-animation-iteration-count:infinite;-ms-animation-name:am-weather-rain;-ms-animation-timing-function:linear;-webkit-animation-duration:8s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-rain;-webkit-animation-timing-function:linear" />
 | 
					 | 
				
			||||||
      <line class="am-weather-rain-2" transform="translate(0,-1)" x1="34" x2="34" y1="46" y2="54"
 | 
					 | 
				
			||||||
        style="-moz-animation-delay:0.25s;-moz-animation-duration:8s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-rain;-moz-animation-timing-function:linear;-ms-animation-delay:0.25s;-ms-animation-duration:8s;-ms-animation-iteration-count:infinite;-ms-animation-name:am-weather-rain;-ms-animation-timing-function:linear;-webkit-animation-delay:0.25s;-webkit-animation-duration:8s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-rain;-webkit-animation-timing-function:linear" />
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
  </g>
 | 
					 | 
				
			||||||
</svg>
 | 
					 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 11 KiB  | 
@@ -1,206 +0,0 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
					 | 
				
			||||||
<!-- (c) ammap.com | SVG weather icons -->
 | 
					 | 
				
			||||||
<svg width="56" height="48" version="1.1" xmlns="http://www.w3.org/2000/svg">
 | 
					 | 
				
			||||||
  <defs>
 | 
					 | 
				
			||||||
    <filter id="blur" x="-.24684" y="-.22892" width="1.4937" height="1.5576">
 | 
					 | 
				
			||||||
      <feGaussianBlur in="SourceAlpha" stdDeviation="3" />
 | 
					 | 
				
			||||||
      <feOffset dx="0" dy="4" result="offsetblur" />
 | 
					 | 
				
			||||||
      <feComponentTransfer>
 | 
					 | 
				
			||||||
        <feFuncA slope="0.05" type="linear" />
 | 
					 | 
				
			||||||
      </feComponentTransfer>
 | 
					 | 
				
			||||||
      <feMerge>
 | 
					 | 
				
			||||||
        <feMergeNode />
 | 
					 | 
				
			||||||
        <feMergeNode in="SourceGraphic" />
 | 
					 | 
				
			||||||
      </feMerge>
 | 
					 | 
				
			||||||
    </filter>
 | 
					 | 
				
			||||||
    <style type="text/css">
 | 
					 | 
				
			||||||
      <![CDATA[
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** SUN
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-sun {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          -webkit-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -moz-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -ms-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          transform: rotate(0deg);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          -webkit-transform: rotate(360deg);
 | 
					 | 
				
			||||||
          -moz-transform: rotate(360deg);
 | 
					 | 
				
			||||||
          -ms-transform: rotate(360deg);
 | 
					 | 
				
			||||||
          transform: rotate(360deg);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-sun {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-sun;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-sun;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-sun;
 | 
					 | 
				
			||||||
        animation-name: am-weather-sun;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 9s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 9s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 9s;
 | 
					 | 
				
			||||||
        animation-duration: 9s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** RAIN
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-rain {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          stroke-dashoffset: 0;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          stroke-dashoffset: -100;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-rain-1 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-rain;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-rain;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-rain;
 | 
					 | 
				
			||||||
        animation-name: am-weather-rain;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 8s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 8s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 8s;
 | 
					 | 
				
			||||||
        animation-duration: 8s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-rain-2 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-rain;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-rain;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-rain;
 | 
					 | 
				
			||||||
        animation-name: am-weather-rain;
 | 
					 | 
				
			||||||
        -webkit-animation-delay: 0.25s;
 | 
					 | 
				
			||||||
        -moz-animation-delay: 0.25s;
 | 
					 | 
				
			||||||
        -ms-animation-delay: 0.25s;
 | 
					 | 
				
			||||||
        animation-delay: 0.25s;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 8s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 8s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 8s;
 | 
					 | 
				
			||||||
        animation-duration: 8s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** CLOUDS
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-cloud-2 {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        50% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-cloud-2 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-cloud-2;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-cloud-2;
 | 
					 | 
				
			||||||
        animation-name: am-weather-cloud-2;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 3s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 3s;
 | 
					 | 
				
			||||||
        animation-duration: 3s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      ]]>
 | 
					 | 
				
			||||||
    </style>
 | 
					 | 
				
			||||||
  </defs>
 | 
					 | 
				
			||||||
  <g transform="translate(16,-2)" filter="url(#blur)">
 | 
					 | 
				
			||||||
    <g transform="translate(0,16)">
 | 
					 | 
				
			||||||
      <g class="am-weather-sun"
 | 
					 | 
				
			||||||
        style="-moz-animation-duration:9s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-sun;-moz-animation-timing-function:linear;-ms-animation-duration:9s;-ms-animation-iteration-count:infinite;-ms-animation-name:am-weather-sun;-ms-animation-timing-function:linear;-webkit-animation-duration:9s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-sun;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
        <line transform="translate(0,9)" y2="3" stroke="#ffa500" stroke-linecap="round" stroke-width="2" fifll="none" />
 | 
					 | 
				
			||||||
        <g transform="rotate(45)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="rotate(90)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="rotate(135)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="scale(-1)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="rotate(225)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="rotate(-90)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="rotate(-45)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <circle r="5" fill="#ffa500" stroke="#ffa500" stroke-width="2" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
    <g class="am-weather-cloud-3"
 | 
					 | 
				
			||||||
      style="-moz-animation-duration:3s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-cloud-2;-moz-animation-timing-function:linear;-webkit-animation-duration:3s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-cloud-2;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
      <path transform="translate(-20,-11)"
 | 
					 | 
				
			||||||
        d="m47.7 35.4c0-4.6-3.7-8.2-8.2-8.2-1 0-1.9 0.2-2.8 0.5-0.3-3.4-3.1-6.2-6.6-6.2-3.7 0-6.7 3-6.7 6.7 0 0.8 0.2 1.6 0.4 2.3-0.3-0.1-0.7-0.1-1-0.1-3.7 0-6.7 3-6.7 6.7 0 3.6 2.9 6.6 6.5 6.7h17.2c4.4-0.5 7.9-4 7.9-8.4z"
 | 
					 | 
				
			||||||
        fill="#57a0ee" stroke="#fff" stroke-linejoin="round" stroke-width="1.2" />
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
    <g transform="translate(-20,-10) rotate(10,-247.39,200.17)" fill="none" stroke="#91c0f8" stroke-dasharray="4, 4"
 | 
					 | 
				
			||||||
      stroke-linecap="round" stroke-width="2">
 | 
					 | 
				
			||||||
      <line class="am-weather-rain-1" transform="translate(-4,1)" y2="8"
 | 
					 | 
				
			||||||
        style="-moz-animation-duration:8s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-rain;-moz-animation-timing-function:linear;-ms-animation-duration:8s;-ms-animation-iteration-count:infinite;-ms-animation-name:am-weather-rain;-ms-animation-timing-function:linear;-webkit-animation-duration:8s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-rain;-webkit-animation-timing-function:linear" />
 | 
					 | 
				
			||||||
      <line class="am-weather-rain-2" transform="translate(0,-1)" y2="8"
 | 
					 | 
				
			||||||
        style="-moz-animation-delay:0.25s;-moz-animation-duration:8s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-rain;-moz-animation-timing-function:linear;-ms-animation-delay:0.25s;-ms-animation-duration:8s;-ms-animation-iteration-count:infinite;-ms-animation-name:am-weather-rain;-ms-animation-timing-function:linear;-webkit-animation-delay:0.25s;-webkit-animation-duration:8s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-rain;-webkit-animation-timing-function:linear" />
 | 
					 | 
				
			||||||
      <line class="am-weather-rain-1" transform="translate(4)" y2="8"
 | 
					 | 
				
			||||||
        style="-moz-animation-duration:8s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-rain;-moz-animation-timing-function:linear;-ms-animation-duration:8s;-ms-animation-iteration-count:infinite;-ms-animation-name:am-weather-rain;-ms-animation-timing-function:linear;-webkit-animation-duration:8s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-rain;-webkit-animation-timing-function:linear" />
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
  </g>
 | 
					 | 
				
			||||||
</svg>
 | 
					 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 9.3 KiB  | 
@@ -1,270 +0,0 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
					 | 
				
			||||||
<!-- (c) ammap.com | SVG weather icons -->
 | 
					 | 
				
			||||||
<svg width="56" height="48" version="1.1" xmlns="http://www.w3.org/2000/svg">
 | 
					 | 
				
			||||||
  <defs>
 | 
					 | 
				
			||||||
    <filter id="blur" x="-.24684" y="-.22892" width="1.4937" height="1.5576">
 | 
					 | 
				
			||||||
      <feGaussianBlur in="SourceAlpha" stdDeviation="3" />
 | 
					 | 
				
			||||||
      <feOffset dx="0" dy="4" result="offsetblur" />
 | 
					 | 
				
			||||||
      <feComponentTransfer>
 | 
					 | 
				
			||||||
        <feFuncA slope="0.05" type="linear" />
 | 
					 | 
				
			||||||
      </feComponentTransfer>
 | 
					 | 
				
			||||||
      <feMerge>
 | 
					 | 
				
			||||||
        <feMergeNode />
 | 
					 | 
				
			||||||
        <feMergeNode in="SourceGraphic" />
 | 
					 | 
				
			||||||
      </feMerge>
 | 
					 | 
				
			||||||
    </filter>
 | 
					 | 
				
			||||||
    <style type="text/css">
 | 
					 | 
				
			||||||
      <![CDATA[
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** RAIN
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-rain {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          stroke-dashoffset: 0;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          stroke-dashoffset: -100;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-rain-1 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-rain;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-rain;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-rain;
 | 
					 | 
				
			||||||
        animation-name: am-weather-rain;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 8s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 8s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 8s;
 | 
					 | 
				
			||||||
        animation-duration: 8s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-rain-2 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-rain;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-rain;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-rain;
 | 
					 | 
				
			||||||
        animation-name: am-weather-rain;
 | 
					 | 
				
			||||||
        -webkit-animation-delay: 0.25s;
 | 
					 | 
				
			||||||
        -moz-animation-delay: 0.25s;
 | 
					 | 
				
			||||||
        -ms-animation-delay: 0.25s;
 | 
					 | 
				
			||||||
        animation-delay: 0.25s;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 8s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 8s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 8s;
 | 
					 | 
				
			||||||
        animation-duration: 8s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** MOON
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-moon {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          -webkit-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -moz-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -ms-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          transform: rotate(0deg);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        50% {
 | 
					 | 
				
			||||||
          -webkit-transform: rotate(15deg);
 | 
					 | 
				
			||||||
          -moz-transform: rotate(15deg);
 | 
					 | 
				
			||||||
          -ms-transform: rotate(15deg);
 | 
					 | 
				
			||||||
          transform: rotate(15deg);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          -webkit-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -moz-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -ms-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          transform: rotate(0deg);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-moon {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-moon;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-moon;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-moon;
 | 
					 | 
				
			||||||
        animation-name: am-weather-moon;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 6s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 6s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 6s;
 | 
					 | 
				
			||||||
        animation-duration: 6s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -webkit-transform-origin: 12.5px 15.15px 0;
 | 
					 | 
				
			||||||
        /* TODO FF CENTER ISSUE */
 | 
					 | 
				
			||||||
        -moz-transform-origin: 12.5px 15.15px 0;
 | 
					 | 
				
			||||||
        /* TODO FF CENTER ISSUE */
 | 
					 | 
				
			||||||
        -ms-transform-origin: 12.5px 15.15px 0;
 | 
					 | 
				
			||||||
        /* TODO FF CENTER ISSUE */
 | 
					 | 
				
			||||||
        transform-origin: 12.5px 15.15px 0;
 | 
					 | 
				
			||||||
        /* TODO FF CENTER ISSUE */
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      @keyframes am-weather-moon-star-1 {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          opacity: 0;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          opacity: 1;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-moon-star-1 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-moon-star-1;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-moon-star-1;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-moon-star-1;
 | 
					 | 
				
			||||||
        animation-name: am-weather-moon-star-1;
 | 
					 | 
				
			||||||
        -webkit-animation-delay: 3s;
 | 
					 | 
				
			||||||
        -moz-animation-delay: 3s;
 | 
					 | 
				
			||||||
        -ms-animation-delay: 3s;
 | 
					 | 
				
			||||||
        animation-delay: 3s;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 5s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 5s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 5s;
 | 
					 | 
				
			||||||
        animation-duration: 5s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        animation-iteration-count: 1;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      @keyframes am-weather-moon-star-2 {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          opacity: 0;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          opacity: 1;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-moon-star-2 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-moon-star-2;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-moon-star-2;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-moon-star-2;
 | 
					 | 
				
			||||||
        animation-name: am-weather-moon-star-2;
 | 
					 | 
				
			||||||
        -webkit-animation-delay: 5s;
 | 
					 | 
				
			||||||
        -moz-animation-delay: 5s;
 | 
					 | 
				
			||||||
        -ms-animation-delay: 5s;
 | 
					 | 
				
			||||||
        animation-delay: 5s;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 4s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 4s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 4s;
 | 
					 | 
				
			||||||
        animation-duration: 4s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        animation-iteration-count: 1;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** CLOUDS
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-cloud-2 {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        50% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-cloud-2 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-cloud-2;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-cloud-2;
 | 
					 | 
				
			||||||
        animation-name: am-weather-cloud-2;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 3s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 3s;
 | 
					 | 
				
			||||||
        animation-duration: 3s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      ]]>
 | 
					 | 
				
			||||||
    </style>
 | 
					 | 
				
			||||||
  </defs>
 | 
					 | 
				
			||||||
  <g transform="translate(16,-2)" filter="url(#blur)">
 | 
					 | 
				
			||||||
    <g transform="matrix(.8 0 0 .8 16 4)">
 | 
					 | 
				
			||||||
      <g class="am-weather-moon-star-1"
 | 
					 | 
				
			||||||
        style="-moz-animation-delay:3s;-moz-animation-duration:5s;-moz-animation-iteration-count:1;-moz-animation-name:am-weather-moon-star-1;-moz-animation-timing-function:linear;-ms-animation-delay:3s;-ms-animation-duration:5s;-ms-animation-iteration-count:1;-ms-animation-name:am-weather-moon-star-1;-ms-animation-timing-function:linear;-webkit-animation-delay:3s;-webkit-animation-duration:5s;-webkit-animation-iteration-count:1;-webkit-animation-name:am-weather-moon-star-1;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
        <polygon points="4 4 3.3 5.2 2.7 4 1.5 3.3 2.7 2.7 3.3 1.5 4 2.7 5.2 3.3" fill="#ffa500"
 | 
					 | 
				
			||||||
          stroke-miterlimit="10" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
      <g class="am-weather-moon-star-2"
 | 
					 | 
				
			||||||
        style="-moz-animation-delay:5s;-moz-animation-duration:4s;-moz-animation-iteration-count:1;-moz-animation-name:am-weather-moon-star-2;-moz-animation-timing-function:linear;-ms-animation-delay:5s;-ms-animation-duration:4s;-ms-animation-iteration-count:1;-ms-animation-name:am-weather-moon-star-2;-ms-animation-timing-function:linear;-webkit-animation-delay:5s;-webkit-animation-duration:4s;-webkit-animation-iteration-count:1;-webkit-animation-name:am-weather-moon-star-2;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
        <polygon transform="translate(20,10)" points="4 4 3.3 5.2 2.7 4 1.5 3.3 2.7 2.7 3.3 1.5 4 2.7 5.2 3.3"
 | 
					 | 
				
			||||||
          fill="#ffa500" stroke-miterlimit="10" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
      <g class="am-weather-moon"
 | 
					 | 
				
			||||||
        style="-moz-animation-duration:6s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-moon;-moz-animation-timing-function:linear;-moz-transform-origin:12.5px 15.15px 0;-ms-animation-duration:6s;-ms-animation-iteration-count:infinite;-ms-animation-name:am-weather-moon;-ms-animation-timing-function:linear;-ms-transform-origin:12.5px 15.15px 0;-webkit-animation-duration:6s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-moon;-webkit-animation-timing-function:linear;-webkit-transform-origin:12.5px 15.15px 0">
 | 
					 | 
				
			||||||
        <path
 | 
					 | 
				
			||||||
          d="m14.5 13.2c0-3.7 2-6.9 5-8.7-1.5-0.9-3.2-1.3-5-1.3-5.5 0-10 4.5-10 10s4.5 10 10 10c1.8 0 3.5-0.5 5-1.3-3-1.7-5-5-5-8.7z"
 | 
					 | 
				
			||||||
          fill="#ffa500" stroke="#ffa500" stroke-linejoin="round" stroke-width="2" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
    <g class="am-weather-cloud-3"
 | 
					 | 
				
			||||||
      style="-moz-animation-duration:3s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-cloud-2;-moz-animation-timing-function:linear;-webkit-animation-duration:3s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-cloud-2;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
      <path transform="translate(-20,-11)"
 | 
					 | 
				
			||||||
        d="m47.7 35.4c0-4.6-3.7-8.2-8.2-8.2-1 0-1.9 0.2-2.8 0.5-0.3-3.4-3.1-6.2-6.6-6.2-3.7 0-6.7 3-6.7 6.7 0 0.8 0.2 1.6 0.4 2.3-0.3-0.1-0.7-0.1-1-0.1-3.7 0-6.7 3-6.7 6.7 0 3.6 2.9 6.6 6.5 6.7h17.2c4.4-0.5 7.9-4 7.9-8.4z"
 | 
					 | 
				
			||||||
        fill="#57a0ee" stroke="#fff" stroke-linejoin="round" stroke-width="1.2" />
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
    <g transform="translate(-20,-10) rotate(10,-247.39,200.17)" fill="none" stroke="#91c0f8" stroke-dasharray="4, 4"
 | 
					 | 
				
			||||||
      stroke-linecap="round" stroke-width="2">
 | 
					 | 
				
			||||||
      <line class="am-weather-rain-1" transform="translate(-4,1)" y2="8"
 | 
					 | 
				
			||||||
        style="-moz-animation-duration:8s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-rain;-moz-animation-timing-function:linear;-ms-animation-duration:8s;-ms-animation-iteration-count:infinite;-ms-animation-name:am-weather-rain;-ms-animation-timing-function:linear;-webkit-animation-duration:8s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-rain;-webkit-animation-timing-function:linear" />
 | 
					 | 
				
			||||||
      <line class="am-weather-rain-2" transform="translate(0,-1)" y2="8"
 | 
					 | 
				
			||||||
        style="-moz-animation-delay:0.25s;-moz-animation-duration:8s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-rain;-moz-animation-timing-function:linear;-ms-animation-delay:0.25s;-ms-animation-duration:8s;-ms-animation-iteration-count:infinite;-ms-animation-name:am-weather-rain;-ms-animation-timing-function:linear;-webkit-animation-delay:0.25s;-webkit-animation-duration:8s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-rain;-webkit-animation-timing-function:linear" />
 | 
					 | 
				
			||||||
      <line class="am-weather-rain-1" transform="translate(4)" y2="8"
 | 
					 | 
				
			||||||
        style="-moz-animation-duration:8s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-rain;-moz-animation-timing-function:linear;-ms-animation-duration:8s;-ms-animation-iteration-count:infinite;-ms-animation-name:am-weather-rain;-ms-animation-timing-function:linear;-webkit-animation-duration:8s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-rain;-webkit-animation-timing-function:linear" />
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
  </g>
 | 
					 | 
				
			||||||
</svg>
 | 
					 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 12 KiB  | 
@@ -1,374 +0,0 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
					 | 
				
			||||||
<!-- (c) ammap.com | SVG weather icons -->
 | 
					 | 
				
			||||||
<!-- Scattered Thunderstorms | Contributed by hsoJ95 on GitHub: https://github.com/hsoj95 -->
 | 
					 | 
				
			||||||
<svg width="56" height="48" version="1.1" xmlns="http://www.w3.org/2000/svg">
 | 
					 | 
				
			||||||
  <defs>
 | 
					 | 
				
			||||||
    <filter id="blur" x="-.20655" y="-.1975" width="1.403" height="1.4766">
 | 
					 | 
				
			||||||
      <feGaussianBlur in="SourceAlpha" stdDeviation="3" />
 | 
					 | 
				
			||||||
      <feOffset dx="0" dy="4" result="offsetblur" />
 | 
					 | 
				
			||||||
      <feComponentTransfer>
 | 
					 | 
				
			||||||
        <feFuncA slope="0.05" type="linear" />
 | 
					 | 
				
			||||||
      </feComponentTransfer>
 | 
					 | 
				
			||||||
      <feMerge>
 | 
					 | 
				
			||||||
        <feMergeNode />
 | 
					 | 
				
			||||||
        <feMergeNode in="SourceGraphic" />
 | 
					 | 
				
			||||||
      </feMerge>
 | 
					 | 
				
			||||||
    </filter>
 | 
					 | 
				
			||||||
    <style type="text/css">
 | 
					 | 
				
			||||||
      <![CDATA[
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** CLOUDS
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-cloud-3 {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(-5px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(-5px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(-5px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(-5px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        50% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(10px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(10px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(10px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(10px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(-5px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(-5px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(-5px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(-5px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-cloud-3 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-cloud-3;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-cloud-3;
 | 
					 | 
				
			||||||
        animation-name: am-weather-cloud-3;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 7s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 7s;
 | 
					 | 
				
			||||||
        animation-duration: 7s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      @keyframes am-weather-cloud-2 {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        50% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-cloud-2 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-cloud-2;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-cloud-2;
 | 
					 | 
				
			||||||
        animation-name: am-weather-cloud-2;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 3s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 3s;
 | 
					 | 
				
			||||||
        animation-duration: 3s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** SUN
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-sun {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          -webkit-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -moz-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -ms-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          transform: rotate(0deg);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          -webkit-transform: rotate(360deg);
 | 
					 | 
				
			||||||
          -moz-transform: rotate(360deg);
 | 
					 | 
				
			||||||
          -ms-transform: rotate(360deg);
 | 
					 | 
				
			||||||
          transform: rotate(360deg);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-sun {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-sun;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-sun;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-sun;
 | 
					 | 
				
			||||||
        animation-name: am-weather-sun;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 9s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 9s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 9s;
 | 
					 | 
				
			||||||
        animation-duration: 9s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      @keyframes am-weather-sun-shiny {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          stroke-dasharray: 3px 10px;
 | 
					 | 
				
			||||||
          stroke-dashoffset: 0px;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        50% {
 | 
					 | 
				
			||||||
          stroke-dasharray: 0.1px 10px;
 | 
					 | 
				
			||||||
          stroke-dashoffset: -1px;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          stroke-dasharray: 3px 10px;
 | 
					 | 
				
			||||||
          stroke-dashoffset: 0px;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-sun-shiny line {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-sun-shiny;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-sun-shiny;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-sun-shiny;
 | 
					 | 
				
			||||||
        animation-name: am-weather-sun-shiny;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 2s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 2s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 2s;
 | 
					 | 
				
			||||||
        animation-duration: 2s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** STROKE
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-stroke {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        2% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        4% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        6% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0.5px, 0.4px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0.5px, 0.4px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0.5px, 0.4px);
 | 
					 | 
				
			||||||
          transform: translate(0.5px, 0.4px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        8% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        10% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        12% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        14% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        16% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        18% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        20% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        22% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(1px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(1px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(1px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(1px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        24% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        26% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(-1px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(-1px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(-1px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(-1px, 0.0px);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        28% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        40% {
 | 
					 | 
				
			||||||
          fill: orange;
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        65% {
 | 
					 | 
				
			||||||
          fill: white;
 | 
					 | 
				
			||||||
          -webkit-transform: translate(-1px, 5.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(-1px, 5.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(-1px, 5.0px);
 | 
					 | 
				
			||||||
          transform: translate(-1px, 5.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        61% {
 | 
					 | 
				
			||||||
          fill: orange;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-stroke {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-stroke;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-stroke;
 | 
					 | 
				
			||||||
        animation-name: am-weather-stroke;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 1.11s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 1.11s;
 | 
					 | 
				
			||||||
        animation-duration: 1.11s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      ]]>
 | 
					 | 
				
			||||||
    </style>
 | 
					 | 
				
			||||||
  </defs>
 | 
					 | 
				
			||||||
  <g id="thunder" transform="translate(16,-2)" filter="url(#blur)">
 | 
					 | 
				
			||||||
    <g transform="translate(0,16)">
 | 
					 | 
				
			||||||
      <g class="am-weather-sun"
 | 
					 | 
				
			||||||
        style="-moz-animation-duration:9s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-sun;-moz-animation-timing-function:linear;-ms-animation-duration:9s;-ms-animation-iteration-count:infinite;-ms-animation-name:am-weather-sun;-ms-animation-timing-function:linear;-webkit-animation-duration:9s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-sun;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
        <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round" stroke-width="2" />
 | 
					 | 
				
			||||||
        <g transform="rotate(45)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="rotate(90)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="rotate(135)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="scale(-1)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="rotate(225)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="rotate(-90)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="rotate(-45)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <circle r="5" fill="#ffa500" stroke="#ffa500" stroke-width="2" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
    <g class="am-weather-cloud-3">
 | 
					 | 
				
			||||||
      <path transform="translate(-20,-11)"
 | 
					 | 
				
			||||||
        d="m47.7 35.4c0-4.6-3.7-8.2-8.2-8.2-1 0-1.9 0.2-2.8 0.5-0.3-3.4-3.1-6.2-6.6-6.2-3.7 0-6.7 3-6.7 6.7 0 0.8 0.2 1.6 0.4 2.3-0.3-0.1-0.7-0.1-1-0.1-3.7 0-6.7 3-6.7 6.7 0 3.6 2.9 6.6 6.5 6.7h17.2c4.4-0.5 7.9-4 7.9-8.4z"
 | 
					 | 
				
			||||||
        fill="#57a0ee" stroke="#fff" stroke-linejoin="round" stroke-width="1.2" />
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
    <g class="am-weather-lightning" transform="matrix(1.2,0,0,1.2,-4,28)">
 | 
					 | 
				
			||||||
      <polygon class="am-weather-stroke" points="11.1 6.9 14.3 -2.9 20.5 -2.9 16.4 4.3 20.3 4.3 11.5 14.6 14.9 6.9"
 | 
					 | 
				
			||||||
        fill="#ffa500" stroke="#fff"
 | 
					 | 
				
			||||||
        style="-moz-animation-duration:1.11s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-stroke;-moz-animation-timing-function:linear;-webkit-animation-duration:1.11s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-stroke;-webkit-animation-timing-function:linear" />
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
  </g>
 | 
					 | 
				
			||||||
</svg>
 | 
					 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 13 KiB  | 
@@ -1,283 +0,0 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
					 | 
				
			||||||
<!-- (c) ammap.com | SVG weather icons -->
 | 
					 | 
				
			||||||
<!-- Scattered Thunderstorms | Contributed by hsoJ95 on GitHub: https://github.com/hsoj95 -->
 | 
					 | 
				
			||||||
<svg width="56" height="48" version="1.1" xmlns="http://www.w3.org/2000/svg">
 | 
					 | 
				
			||||||
  <defs>
 | 
					 | 
				
			||||||
    <filter id="blur" x="-.20655" y="-.1975" width="1.403" height="1.4766">
 | 
					 | 
				
			||||||
      <feGaussianBlur in="SourceAlpha" stdDeviation="3" />
 | 
					 | 
				
			||||||
      <feOffset dx="0" dy="4" result="offsetblur" />
 | 
					 | 
				
			||||||
      <feComponentTransfer>
 | 
					 | 
				
			||||||
        <feFuncA slope="0.05" type="linear" />
 | 
					 | 
				
			||||||
      </feComponentTransfer>
 | 
					 | 
				
			||||||
      <feMerge>
 | 
					 | 
				
			||||||
        <feMergeNode />
 | 
					 | 
				
			||||||
        <feMergeNode in="SourceGraphic" />
 | 
					 | 
				
			||||||
      </feMerge>
 | 
					 | 
				
			||||||
    </filter>
 | 
					 | 
				
			||||||
    <style type="text/css">
 | 
					 | 
				
			||||||
      <![CDATA[
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** CLOUDS
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-cloud-3 {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(-5px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(-5px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(-5px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(-5px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        50% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(10px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(10px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(10px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(10px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(-5px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(-5px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(-5px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(-5px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-cloud-3 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-cloud-3;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-cloud-3;
 | 
					 | 
				
			||||||
        animation-name: am-weather-cloud-3;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 7s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 7s;
 | 
					 | 
				
			||||||
        animation-duration: 7s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      @keyframes am-weather-cloud-2 {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        50% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-cloud-2 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-cloud-2;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-cloud-2;
 | 
					 | 
				
			||||||
        animation-name: am-weather-cloud-2;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 3s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 3s;
 | 
					 | 
				
			||||||
        animation-duration: 3s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** STROKE
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-stroke {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        2% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        4% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        6% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0.5px, 0.4px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0.5px, 0.4px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0.5px, 0.4px);
 | 
					 | 
				
			||||||
          transform: translate(0.5px, 0.4px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        8% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        10% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        12% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        14% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        16% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        18% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        20% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        22% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(1px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(1px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(1px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(1px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        24% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        26% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(-1px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(-1px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(-1px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(-1px, 0.0px);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        28% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        40% {
 | 
					 | 
				
			||||||
          fill: orange;
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        65% {
 | 
					 | 
				
			||||||
          fill: white;
 | 
					 | 
				
			||||||
          -webkit-transform: translate(-1px, 5.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(-1px, 5.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(-1px, 5.0px);
 | 
					 | 
				
			||||||
          transform: translate(-1px, 5.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        61% {
 | 
					 | 
				
			||||||
          fill: orange;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
          transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-stroke {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-stroke;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-stroke;
 | 
					 | 
				
			||||||
        animation-name: am-weather-stroke;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 1.11s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 1.11s;
 | 
					 | 
				
			||||||
        animation-duration: 1.11s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      ]]>
 | 
					 | 
				
			||||||
    </style>
 | 
					 | 
				
			||||||
  </defs>
 | 
					 | 
				
			||||||
  <g id="thunder" transform="translate(16,-2)" filter="url(#blur)">
 | 
					 | 
				
			||||||
    <g transform="matrix(.8 0 0 .8 16 4)">
 | 
					 | 
				
			||||||
      <g class="am-weather-moon-star-1"
 | 
					 | 
				
			||||||
        style="-moz-animation-delay:3s;-moz-animation-duration:5s;-moz-animation-iteration-count:1;-moz-animation-name:am-weather-moon-star-1;-moz-animation-timing-function:linear;-ms-animation-delay:3s;-ms-animation-duration:5s;-ms-animation-iteration-count:1;-ms-animation-name:am-weather-moon-star-1;-ms-animation-timing-function:linear;-webkit-animation-delay:3s;-webkit-animation-duration:5s;-webkit-animation-iteration-count:1;-webkit-animation-name:am-weather-moon-star-1;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
        <polygon points="3.3 1.5 4 2.7 5.2 3.3 4 4 3.3 5.2 2.7 4 1.5 3.3 2.7 2.7" fill="#ffa500"
 | 
					 | 
				
			||||||
          stroke-miterlimit="10" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
      <g class="am-weather-moon-star-2"
 | 
					 | 
				
			||||||
        style="-moz-animation-delay:5s;-moz-animation-duration:4s;-moz-animation-iteration-count:1;-moz-animation-name:am-weather-moon-star-2;-moz-animation-timing-function:linear;-ms-animation-delay:5s;-ms-animation-duration:4s;-ms-animation-iteration-count:1;-ms-animation-name:am-weather-moon-star-2;-ms-animation-timing-function:linear;-webkit-animation-delay:5s;-webkit-animation-duration:4s;-webkit-animation-iteration-count:1;-webkit-animation-name:am-weather-moon-star-2;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
        <polygon transform="translate(20,10)" points="3.3 1.5 4 2.7 5.2 3.3 4 4 3.3 5.2 2.7 4 1.5 3.3 2.7 2.7"
 | 
					 | 
				
			||||||
          fill="#ffa500" stroke-miterlimit="10" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
      <g class="am-weather-moon"
 | 
					 | 
				
			||||||
        style="-moz-animation-duration:6s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-moon;-moz-animation-timing-function:linear;-moz-transform-origin:12.5px 15.15px 0;-ms-animation-duration:6s;-ms-animation-iteration-count:infinite;-ms-animation-name:am-weather-moon;-ms-animation-timing-function:linear;-ms-transform-origin:12.5px 15.15px 0;-webkit-animation-duration:6s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-moon;-webkit-animation-timing-function:linear;-webkit-transform-origin:12.5px 15.15px 0">
 | 
					 | 
				
			||||||
        <path
 | 
					 | 
				
			||||||
          d="m14.5 13.2c0-3.7 2-6.9 5-8.7-1.5-0.9-3.2-1.3-5-1.3-5.5 0-10 4.5-10 10s4.5 10 10 10c1.8 0 3.5-0.5 5-1.3-3-1.7-5-5-5-8.7z"
 | 
					 | 
				
			||||||
          fill="#ffa500" stroke="#ffa500" stroke-linejoin="round" stroke-width="2" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
    <g class="am-weather-cloud-3">
 | 
					 | 
				
			||||||
      <path transform="translate(-20,-11)"
 | 
					 | 
				
			||||||
        d="m47.7 35.4c0-4.6-3.7-8.2-8.2-8.2-1 0-1.9 0.2-2.8 0.5-0.3-3.4-3.1-6.2-6.6-6.2-3.7 0-6.7 3-6.7 6.7 0 0.8 0.2 1.6 0.4 2.3-0.3-0.1-0.7-0.1-1-0.1-3.7 0-6.7 3-6.7 6.7 0 3.6 2.9 6.6 6.5 6.7h17.2c4.4-0.5 7.9-4 7.9-8.4z"
 | 
					 | 
				
			||||||
        fill="#57a0ee" stroke="#fff" stroke-linejoin="round" stroke-width="1.2" />
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
    <g class="am-weather-lightning" transform="matrix(1.2,0,0,1.2,-4,28)">
 | 
					 | 
				
			||||||
      <polygon class="am-weather-stroke" points="11.1 6.9 14.3 -2.9 20.5 -2.9 16.4 4.3 20.3 4.3 11.5 14.6 14.9 6.9"
 | 
					 | 
				
			||||||
        fill="#ffa500" stroke="#fff"
 | 
					 | 
				
			||||||
        style="-moz-animation-duration:1.11s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-stroke;-moz-animation-timing-function:linear;-webkit-animation-duration:1.11s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-stroke;-webkit-animation-timing-function:linear" />
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
  </g>
 | 
					 | 
				
			||||||
</svg>
 | 
					 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 11 KiB  | 
@@ -1,307 +0,0 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
					 | 
				
			||||||
<!-- (c) ammap.com | SVG weather icons -->
 | 
					 | 
				
			||||||
<!-- Severe Thunderstorm | Contributed by hsoJ95 on GitHub: https://github.com/hsoj95 -->
 | 
					 | 
				
			||||||
<svg width="56" height="48" version="1.1" xmlns="http://www.w3.org/2000/svg">
 | 
					 | 
				
			||||||
    <defs>
 | 
					 | 
				
			||||||
        <filter id="blur" x="-.17571" y="-.19575" width="1.3379" height="1.4959">
 | 
					 | 
				
			||||||
            <feGaussianBlur in="SourceAlpha" stdDeviation="3" />
 | 
					 | 
				
			||||||
            <feOffset dx="0" dy="4" result="offsetblur" />
 | 
					 | 
				
			||||||
            <feComponentTransfer>
 | 
					 | 
				
			||||||
                <feFuncA slope="0.05" type="linear" />
 | 
					 | 
				
			||||||
            </feComponentTransfer>
 | 
					 | 
				
			||||||
            <feMerge>
 | 
					 | 
				
			||||||
                <feMergeNode />
 | 
					 | 
				
			||||||
                <feMergeNode in="SourceGraphic" />
 | 
					 | 
				
			||||||
            </feMerge>
 | 
					 | 
				
			||||||
        </filter>
 | 
					 | 
				
			||||||
        <style type="text/css">
 | 
					 | 
				
			||||||
            <![CDATA[
 | 
					 | 
				
			||||||
            /*
 | 
					 | 
				
			||||||
** CLOUDS
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
            @keyframes am-weather-cloud-3 {
 | 
					 | 
				
			||||||
                0% {
 | 
					 | 
				
			||||||
                    -webkit-transform: translate(-5px, 0px);
 | 
					 | 
				
			||||||
                    -moz-transform: translate(-5px, 0px);
 | 
					 | 
				
			||||||
                    -ms-transform: translate(-5px, 0px);
 | 
					 | 
				
			||||||
                    transform: translate(-5px, 0px);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                50% {
 | 
					 | 
				
			||||||
                    -webkit-transform: translate(10px, 0px);
 | 
					 | 
				
			||||||
                    -moz-transform: translate(10px, 0px);
 | 
					 | 
				
			||||||
                    -ms-transform: translate(10px, 0px);
 | 
					 | 
				
			||||||
                    transform: translate(10px, 0px);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                100% {
 | 
					 | 
				
			||||||
                    -webkit-transform: translate(-5px, 0px);
 | 
					 | 
				
			||||||
                    -moz-transform: translate(-5px, 0px);
 | 
					 | 
				
			||||||
                    -ms-transform: translate(-5px, 0px);
 | 
					 | 
				
			||||||
                    transform: translate(-5px, 0px);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            .am-weather-cloud-3 {
 | 
					 | 
				
			||||||
                -webkit-animation-name: am-weather-cloud-3;
 | 
					 | 
				
			||||||
                -moz-animation-name: am-weather-cloud-3;
 | 
					 | 
				
			||||||
                animation-name: am-weather-cloud-3;
 | 
					 | 
				
			||||||
                -webkit-animation-duration: 7s;
 | 
					 | 
				
			||||||
                -moz-animation-duration: 7s;
 | 
					 | 
				
			||||||
                animation-duration: 7s;
 | 
					 | 
				
			||||||
                -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
                -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
                animation-timing-function: linear;
 | 
					 | 
				
			||||||
                -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
                -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
                animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            @keyframes am-weather-cloud-1 {
 | 
					 | 
				
			||||||
                0% {
 | 
					 | 
				
			||||||
                    -webkit-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
                    -moz-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
                    -ms-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
                    transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                50% {
 | 
					 | 
				
			||||||
                    -webkit-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
                    -moz-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
                    -ms-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
                    transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                100% {
 | 
					 | 
				
			||||||
                    -webkit-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
                    -moz-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
                    -ms-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
                    transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            .am-weather-cloud-1 {
 | 
					 | 
				
			||||||
                -webkit-animation-name: am-weather-cloud-1;
 | 
					 | 
				
			||||||
                -moz-animation-name: am-weather-cloud-1;
 | 
					 | 
				
			||||||
                animation-name: am-weather-cloud-1;
 | 
					 | 
				
			||||||
                -webkit-animation-duration: 3s;
 | 
					 | 
				
			||||||
                -moz-animation-duration: 3s;
 | 
					 | 
				
			||||||
                animation-duration: 3s;
 | 
					 | 
				
			||||||
                -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
                -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
                animation-timing-function: linear;
 | 
					 | 
				
			||||||
                -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
                -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
                animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            /*
 | 
					 | 
				
			||||||
** STROKE
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            @keyframes am-weather-stroke {
 | 
					 | 
				
			||||||
                0% {
 | 
					 | 
				
			||||||
                    -webkit-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
                    -moz-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
                    -ms-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
                    transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                2% {
 | 
					 | 
				
			||||||
                    -webkit-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
                    -moz-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
                    -ms-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
                    transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                4% {
 | 
					 | 
				
			||||||
                    -webkit-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
                    -moz-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
                    -ms-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
                    transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                6% {
 | 
					 | 
				
			||||||
                    -webkit-transform: translate(0.5px, 0.4px);
 | 
					 | 
				
			||||||
                    -moz-transform: translate(0.5px, 0.4px);
 | 
					 | 
				
			||||||
                    -ms-transform: translate(0.5px, 0.4px);
 | 
					 | 
				
			||||||
                    transform: translate(0.5px, 0.4px);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                8% {
 | 
					 | 
				
			||||||
                    -webkit-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
                    -moz-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
                    -ms-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
                    transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                10% {
 | 
					 | 
				
			||||||
                    -webkit-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
                    -moz-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
                    -ms-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
                    transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                12% {
 | 
					 | 
				
			||||||
                    -webkit-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
                    -moz-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
                    -ms-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
                    transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                14% {
 | 
					 | 
				
			||||||
                    -webkit-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
                    -moz-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
                    -ms-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
                    transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                16% {
 | 
					 | 
				
			||||||
                    -webkit-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
                    -moz-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
                    -ms-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
                    transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                18% {
 | 
					 | 
				
			||||||
                    -webkit-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
                    -moz-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
                    -ms-transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
                    transform: translate(0.3px, 0.0px);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                20% {
 | 
					 | 
				
			||||||
                    -webkit-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
                    -moz-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
                    -ms-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
                    transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                22% {
 | 
					 | 
				
			||||||
                    -webkit-transform: translate(1px, 0.0px);
 | 
					 | 
				
			||||||
                    -moz-transform: translate(1px, 0.0px);
 | 
					 | 
				
			||||||
                    -ms-transform: translate(1px, 0.0px);
 | 
					 | 
				
			||||||
                    transform: translate(1px, 0.0px);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                24% {
 | 
					 | 
				
			||||||
                    -webkit-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
                    -moz-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
                    -ms-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
                    transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                26% {
 | 
					 | 
				
			||||||
                    -webkit-transform: translate(-1px, 0.0px);
 | 
					 | 
				
			||||||
                    -moz-transform: translate(-1px, 0.0px);
 | 
					 | 
				
			||||||
                    -ms-transform: translate(-1px, 0.0px);
 | 
					 | 
				
			||||||
                    transform: translate(-1px, 0.0px);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                28% {
 | 
					 | 
				
			||||||
                    -webkit-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
                    -moz-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
                    -ms-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
                    transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                40% {
 | 
					 | 
				
			||||||
                    fill: orange;
 | 
					 | 
				
			||||||
                    -webkit-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
                    -moz-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
                    -ms-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
                    transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                65% {
 | 
					 | 
				
			||||||
                    fill: white;
 | 
					 | 
				
			||||||
                    -webkit-transform: translate(-1px, 5.0px);
 | 
					 | 
				
			||||||
                    -moz-transform: translate(-1px, 5.0px);
 | 
					 | 
				
			||||||
                    -ms-transform: translate(-1px, 5.0px);
 | 
					 | 
				
			||||||
                    transform: translate(-1px, 5.0px);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                61% {
 | 
					 | 
				
			||||||
                    fill: orange;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                100% {
 | 
					 | 
				
			||||||
                    -webkit-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
                    -moz-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
                    -ms-transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
                    transform: translate(0.0px, 0.0px);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            .am-weather-stroke {
 | 
					 | 
				
			||||||
                -webkit-animation-name: am-weather-stroke;
 | 
					 | 
				
			||||||
                -moz-animation-name: am-weather-stroke;
 | 
					 | 
				
			||||||
                animation-name: am-weather-stroke;
 | 
					 | 
				
			||||||
                -webkit-animation-duration: 1.11s;
 | 
					 | 
				
			||||||
                -moz-animation-duration: 1.11s;
 | 
					 | 
				
			||||||
                animation-duration: 1.11s;
 | 
					 | 
				
			||||||
                -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
                -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
                animation-timing-function: linear;
 | 
					 | 
				
			||||||
                -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
                -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
                animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            @keyframes error {
 | 
					 | 
				
			||||||
                0% {
 | 
					 | 
				
			||||||
                    fill: #cc0000;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                50% {
 | 
					 | 
				
			||||||
                    fill: #ff0000;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                100% {
 | 
					 | 
				
			||||||
                    fill: #cc0000;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            #Shape {
 | 
					 | 
				
			||||||
                -webkit-animation-name: error;
 | 
					 | 
				
			||||||
                -moz-animation-name: error;
 | 
					 | 
				
			||||||
                animation-name: error;
 | 
					 | 
				
			||||||
                -webkit-animation-duration: 1s;
 | 
					 | 
				
			||||||
                -moz-animation-duration: 1s;
 | 
					 | 
				
			||||||
                animation-duration: 1s;
 | 
					 | 
				
			||||||
                -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
                -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
                animation-timing-function: linear;
 | 
					 | 
				
			||||||
                -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
                -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
                animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            ]]>
 | 
					 | 
				
			||||||
        </style>
 | 
					 | 
				
			||||||
    </defs>
 | 
					 | 
				
			||||||
    <g transform="translate(16,-2)" filter="url(#blur)">
 | 
					 | 
				
			||||||
        <g class="am-weather-cloud-1"
 | 
					 | 
				
			||||||
            style="-moz-animation-duration:7s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-cloud-1;-moz-animation-timing-function:linear;-webkit-animation-duration:7s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-cloud-1;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
            <path transform="matrix(.6 0 0 .6 -10 -6)"
 | 
					 | 
				
			||||||
                d="m47.7 35.4c0-4.6-3.7-8.2-8.2-8.2-1 0-1.9 0.2-2.8 0.5-0.3-3.4-3.1-6.2-6.6-6.2-3.7 0-6.7 3-6.7 6.7 0 0.8 0.2 1.6 0.4 2.3-0.3-0.1-0.7-0.1-1-0.1-3.7 0-6.7 3-6.7 6.7 0 3.6 2.9 6.6 6.5 6.7h17.2c4.4-0.5 7.9-4 7.9-8.4z"
 | 
					 | 
				
			||||||
                fill="#666" stroke="#fff" stroke-linejoin="round" stroke-width="1.2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g class="am-weather-cloud-3">
 | 
					 | 
				
			||||||
            <path transform="translate(-20,-11)"
 | 
					 | 
				
			||||||
                d="m47.7 35.4c0-4.6-3.7-8.2-8.2-8.2-1 0-1.9 0.2-2.8 0.5-0.3-3.4-3.1-6.2-6.6-6.2-3.7 0-6.7 3-6.7 6.7 0 0.8 0.2 1.6 0.4 2.3-0.3-0.1-0.7-0.1-1-0.1-3.7 0-6.7 3-6.7 6.7 0 3.6 2.9 6.6 6.5 6.7h17.2c4.4-0.5 7.9-4 7.9-8.4z"
 | 
					 | 
				
			||||||
                fill="#333" stroke="#fff" stroke-linejoin="round" stroke-width="1.2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="matrix(1.2,0,0,1.2,-4,28)">
 | 
					 | 
				
			||||||
            <polygon class="am-weather-stroke"
 | 
					 | 
				
			||||||
                points="11.1 6.9 14.3 -2.9 20.5 -2.9 16.4 4.3 20.3 4.3 11.5 14.6 14.9 6.9" fill="#ffa500" stroke="#fff"
 | 
					 | 
				
			||||||
                style="-moz-animation-duration:1.11s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-stroke;-moz-animation-timing-function:linear;-webkit-animation-duration:1.11s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-stroke;-webkit-animation-timing-function:linear" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g class="warning" transform="translate(20,30)">
 | 
					 | 
				
			||||||
            <path
 | 
					 | 
				
			||||||
                d="m7.7791 2.906-5.9912 10.117c-0.56283 0.95042-0.24862 2.1772 0.7018 2.74 0.30853 0.18271 0.66051 0.27911 1.0191 0.27911h11.982c1.1046 0 2-0.89543 2-2 0-0.35857-0.0964-0.71056-0.27911-1.0191l-5.9912-10.117c-0.56283-0.95042-1.7896-1.2646-2.74-0.7018-0.28918 0.17125-0.53055 0.41262-0.7018 0.7018z"
 | 
					 | 
				
			||||||
                fill="#c00" />
 | 
					 | 
				
			||||||
            <path d="m9.5 10.5v-5" stroke="#fff" stroke-linecap="round" stroke-width="1.5" />
 | 
					 | 
				
			||||||
            <circle cx="9.5" cy="13" r="1" fill="#fff" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
</svg>
 | 
					 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 13 KiB  | 
@@ -1,241 +0,0 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
					 | 
				
			||||||
<!-- (c) ammap.com | SVG weather icons -->
 | 
					 | 
				
			||||||
<svg width="56" height="48" version="1.1" xmlns="http://www.w3.org/2000/svg">
 | 
					 | 
				
			||||||
  <defs>
 | 
					 | 
				
			||||||
    <filter id="blur" x="-.20655" y="-.23099" width="1.403" height="1.5634">
 | 
					 | 
				
			||||||
      <feGaussianBlur in="SourceAlpha" stdDeviation="3" />
 | 
					 | 
				
			||||||
      <feOffset dx="0" dy="4" result="offsetblur" />
 | 
					 | 
				
			||||||
      <feComponentTransfer>
 | 
					 | 
				
			||||||
        <feFuncA slope="0.05" type="linear" />
 | 
					 | 
				
			||||||
      </feComponentTransfer>
 | 
					 | 
				
			||||||
      <feMerge>
 | 
					 | 
				
			||||||
        <feMergeNode />
 | 
					 | 
				
			||||||
        <feMergeNode in="SourceGraphic" />
 | 
					 | 
				
			||||||
      </feMerge>
 | 
					 | 
				
			||||||
    </filter>
 | 
					 | 
				
			||||||
    <style type="text/css">
 | 
					 | 
				
			||||||
      <![CDATA[
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** CLOUDS
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-cloud-2 {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        50% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-cloud-2 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-cloud-2;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-cloud-2;
 | 
					 | 
				
			||||||
        animation-name: am-weather-cloud-2;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 3s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 3s;
 | 
					 | 
				
			||||||
        animation-duration: 3s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** SUN
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-sun {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          -webkit-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -moz-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -ms-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          transform: rotate(0deg);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          -webkit-transform: rotate(360deg);
 | 
					 | 
				
			||||||
          -moz-transform: rotate(360deg);
 | 
					 | 
				
			||||||
          -ms-transform: rotate(360deg);
 | 
					 | 
				
			||||||
          transform: rotate(360deg);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-sun {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-sun;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-sun;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-sun;
 | 
					 | 
				
			||||||
        animation-name: am-weather-sun;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 9s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 9s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 9s;
 | 
					 | 
				
			||||||
        animation-duration: 9s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      @keyframes am-weather-sun-shiny {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          stroke-dasharray: 3px 10px;
 | 
					 | 
				
			||||||
          stroke-dashoffset: 0px;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        50% {
 | 
					 | 
				
			||||||
          stroke-dasharray: 0.1px 10px;
 | 
					 | 
				
			||||||
          stroke-dashoffset: -1px;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          stroke-dasharray: 3px 10px;
 | 
					 | 
				
			||||||
          stroke-dashoffset: 0px;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-sun-shiny line {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-sun-shiny;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-sun-shiny;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-sun-shiny;
 | 
					 | 
				
			||||||
        animation-name: am-weather-sun-shiny;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 2s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 2s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 2s;
 | 
					 | 
				
			||||||
        animation-duration: 2s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** SNOW
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-snow {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          -webkit-transform: translateX(0) translateY(0);
 | 
					 | 
				
			||||||
          -moz-transform: translateX(0) translateY(0);
 | 
					 | 
				
			||||||
          -ms-transform: translateX(0) translateY(0);
 | 
					 | 
				
			||||||
          transform: translateX(0) translateY(0);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        33.33% {
 | 
					 | 
				
			||||||
          -webkit-transform: translateX(-1.2px) translateY(2px);
 | 
					 | 
				
			||||||
          -moz-transform: translateX(-1.2px) translateY(2px);
 | 
					 | 
				
			||||||
          -ms-transform: translateX(-1.2px) translateY(2px);
 | 
					 | 
				
			||||||
          transform: translateX(-1.2px) translateY(2px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        66.66% {
 | 
					 | 
				
			||||||
          -webkit-transform: translateX(1.4px) translateY(4px);
 | 
					 | 
				
			||||||
          -moz-transform: translateX(1.4px) translateY(4px);
 | 
					 | 
				
			||||||
          -ms-transform: translateX(1.4px) translateY(4px);
 | 
					 | 
				
			||||||
          transform: translateX(1.4px) translateY(4px);
 | 
					 | 
				
			||||||
          opacity: 1;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          -webkit-transform: translateX(-1.6px) translateY(6px);
 | 
					 | 
				
			||||||
          -moz-transform: translateX(-1.6px) translateY(6px);
 | 
					 | 
				
			||||||
          -ms-transform: translateX(-1.6px) translateY(6px);
 | 
					 | 
				
			||||||
          transform: translateX(-1.6px) translateY(6px);
 | 
					 | 
				
			||||||
          opacity: 0;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-snow-1 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-snow;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-snow;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-snow;
 | 
					 | 
				
			||||||
        animation-name: am-weather-snow;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 2s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 2s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 2s;
 | 
					 | 
				
			||||||
        animation-duration: 2s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      ]]>
 | 
					 | 
				
			||||||
    </style>
 | 
					 | 
				
			||||||
  </defs>
 | 
					 | 
				
			||||||
  <g transform="translate(16,-2)" filter="url(#blur)">
 | 
					 | 
				
			||||||
    <g transform="translate(0,16)">
 | 
					 | 
				
			||||||
      <g class="am-weather-sun" transform="translate(0,16)"
 | 
					 | 
				
			||||||
        style="-moz-animation-duration:9s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-sun;-moz-animation-timing-function:linear;-ms-animation-duration:9s;-ms-animation-iteration-count:infinite;-ms-animation-name:am-weather-sun;-ms-animation-timing-function:linear;-webkit-animation-duration:9s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-sun;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
        <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round" stroke-width="2" />
 | 
					 | 
				
			||||||
        <g transform="rotate(45)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="rotate(90)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="rotate(135)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="scale(-1)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="rotate(225)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="rotate(-90)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="rotate(-45)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <circle r="5" fill="#ffa500" stroke="#ffa500" stroke-width="2" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
    <g class="am-weather-cloud-3"
 | 
					 | 
				
			||||||
      style="-moz-animation-duration:3s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-cloud-2;-moz-animation-timing-function:linear;-webkit-animation-duration:3s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-cloud-2;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
      <path transform="translate(-20,-11)"
 | 
					 | 
				
			||||||
        d="m47.7 35.4c0-4.6-3.7-8.2-8.2-8.2-1 0-1.9 0.2-2.8 0.5-0.3-3.4-3.1-6.2-6.6-6.2-3.7 0-6.7 3-6.7 6.7 0 0.8 0.2 1.6 0.4 2.3-0.3-0.1-0.7-0.1-1-0.1-3.7 0-6.7 3-6.7 6.7 0 3.6 2.9 6.6 6.5 6.7h17.2c4.4-0.5 7.9-4 7.9-8.4z"
 | 
					 | 
				
			||||||
        fill="#57a0ee" stroke="#fff" stroke-linejoin="round" stroke-width="1.2" />
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
    <g class="am-weather-snow-1"
 | 
					 | 
				
			||||||
      style="-moz-animation-duration:2s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-snow;-moz-animation-timing-function:linear;-ms-animation-duration:2s;-ms-animation-iteration-count:infinite;-ms-animation-name:am-weather-snow;-ms-animation-timing-function:linear;-webkit-animation-duration:2s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-snow;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
      <g transform="translate(12,28)" fill="none" stroke="#57a0ee" stroke-linecap="round">
 | 
					 | 
				
			||||||
        <line transform="translate(0,9)" y1="-2.5" y2="2.5" stroke-width="1.2" />
 | 
					 | 
				
			||||||
        <line transform="rotate(45,-10.864,4.5)" y1="-2.5" y2="2.5" />
 | 
					 | 
				
			||||||
        <line transform="rotate(90,-4.5,4.5)" y1="-2.5" y2="2.5" />
 | 
					 | 
				
			||||||
        <line transform="rotate(135,-1.864,4.5)" y1="-2.5" y2="2.5" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
  </g>
 | 
					 | 
				
			||||||
</svg>
 | 
					 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 9.6 KiB  | 
@@ -1,269 +0,0 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
					 | 
				
			||||||
<!-- (c) ammap.com | SVG weather icons -->
 | 
					 | 
				
			||||||
<svg width="56" height="48" version="1.1" xmlns="http://www.w3.org/2000/svg">
 | 
					 | 
				
			||||||
  <defs>
 | 
					 | 
				
			||||||
    <filter id="blur" x="-.20655" y="-.23099" width="1.403" height="1.5634">
 | 
					 | 
				
			||||||
      <feGaussianBlur in="SourceAlpha" stdDeviation="3" />
 | 
					 | 
				
			||||||
      <feOffset dx="0" dy="4" result="offsetblur" />
 | 
					 | 
				
			||||||
      <feComponentTransfer>
 | 
					 | 
				
			||||||
        <feFuncA slope="0.05" type="linear" />
 | 
					 | 
				
			||||||
      </feComponentTransfer>
 | 
					 | 
				
			||||||
      <feMerge>
 | 
					 | 
				
			||||||
        <feMergeNode />
 | 
					 | 
				
			||||||
        <feMergeNode in="SourceGraphic" />
 | 
					 | 
				
			||||||
      </feMerge>
 | 
					 | 
				
			||||||
    </filter>
 | 
					 | 
				
			||||||
    <style type="text/css">
 | 
					 | 
				
			||||||
      <![CDATA[
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** CLOUDS
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-cloud-2 {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        50% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-cloud-2 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-cloud-2;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-cloud-2;
 | 
					 | 
				
			||||||
        animation-name: am-weather-cloud-2;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 3s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 3s;
 | 
					 | 
				
			||||||
        animation-duration: 3s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** MOON
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-moon {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          -webkit-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -moz-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -ms-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          transform: rotate(0deg);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        50% {
 | 
					 | 
				
			||||||
          -webkit-transform: rotate(15deg);
 | 
					 | 
				
			||||||
          -moz-transform: rotate(15deg);
 | 
					 | 
				
			||||||
          -ms-transform: rotate(15deg);
 | 
					 | 
				
			||||||
          transform: rotate(15deg);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          -webkit-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -moz-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -ms-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          transform: rotate(0deg);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-moon {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-moon;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-moon;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-moon;
 | 
					 | 
				
			||||||
        animation-name: am-weather-moon;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 6s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 6s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 6s;
 | 
					 | 
				
			||||||
        animation-duration: 6s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -webkit-transform-origin: 12.5px 15.15px 0;
 | 
					 | 
				
			||||||
        /* TODO FF CENTER ISSUE */
 | 
					 | 
				
			||||||
        -moz-transform-origin: 12.5px 15.15px 0;
 | 
					 | 
				
			||||||
        /* TODO FF CENTER ISSUE */
 | 
					 | 
				
			||||||
        -ms-transform-origin: 12.5px 15.15px 0;
 | 
					 | 
				
			||||||
        /* TODO FF CENTER ISSUE */
 | 
					 | 
				
			||||||
        transform-origin: 12.5px 15.15px 0;
 | 
					 | 
				
			||||||
        /* TODO FF CENTER ISSUE */
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      @keyframes am-weather-moon-star-1 {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          opacity: 0;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          opacity: 1;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-moon-star-1 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-moon-star-1;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-moon-star-1;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-moon-star-1;
 | 
					 | 
				
			||||||
        animation-name: am-weather-moon-star-1;
 | 
					 | 
				
			||||||
        -webkit-animation-delay: 3s;
 | 
					 | 
				
			||||||
        -moz-animation-delay: 3s;
 | 
					 | 
				
			||||||
        -ms-animation-delay: 3s;
 | 
					 | 
				
			||||||
        animation-delay: 3s;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 5s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 5s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 5s;
 | 
					 | 
				
			||||||
        animation-duration: 5s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        animation-iteration-count: 1;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      @keyframes am-weather-moon-star-2 {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          opacity: 0;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          opacity: 1;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-moon-star-2 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-moon-star-2;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-moon-star-2;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-moon-star-2;
 | 
					 | 
				
			||||||
        animation-name: am-weather-moon-star-2;
 | 
					 | 
				
			||||||
        -webkit-animation-delay: 5s;
 | 
					 | 
				
			||||||
        -moz-animation-delay: 5s;
 | 
					 | 
				
			||||||
        -ms-animation-delay: 5s;
 | 
					 | 
				
			||||||
        animation-delay: 5s;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 4s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 4s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 4s;
 | 
					 | 
				
			||||||
        animation-duration: 4s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        animation-iteration-count: 1;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** SNOW
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-snow {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          -webkit-transform: translateX(0) translateY(0);
 | 
					 | 
				
			||||||
          -moz-transform: translateX(0) translateY(0);
 | 
					 | 
				
			||||||
          -ms-transform: translateX(0) translateY(0);
 | 
					 | 
				
			||||||
          transform: translateX(0) translateY(0);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        33.33% {
 | 
					 | 
				
			||||||
          -webkit-transform: translateX(-1.2px) translateY(2px);
 | 
					 | 
				
			||||||
          -moz-transform: translateX(-1.2px) translateY(2px);
 | 
					 | 
				
			||||||
          -ms-transform: translateX(-1.2px) translateY(2px);
 | 
					 | 
				
			||||||
          transform: translateX(-1.2px) translateY(2px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        66.66% {
 | 
					 | 
				
			||||||
          -webkit-transform: translateX(1.4px) translateY(4px);
 | 
					 | 
				
			||||||
          -moz-transform: translateX(1.4px) translateY(4px);
 | 
					 | 
				
			||||||
          -ms-transform: translateX(1.4px) translateY(4px);
 | 
					 | 
				
			||||||
          transform: translateX(1.4px) translateY(4px);
 | 
					 | 
				
			||||||
          opacity: 1;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          -webkit-transform: translateX(-1.6px) translateY(6px);
 | 
					 | 
				
			||||||
          -moz-transform: translateX(-1.6px) translateY(6px);
 | 
					 | 
				
			||||||
          -ms-transform: translateX(-1.6px) translateY(6px);
 | 
					 | 
				
			||||||
          transform: translateX(-1.6px) translateY(6px);
 | 
					 | 
				
			||||||
          opacity: 0;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-snow-1 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-snow;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-snow;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-snow;
 | 
					 | 
				
			||||||
        animation-name: am-weather-snow;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 2s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 2s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 2s;
 | 
					 | 
				
			||||||
        animation-duration: 2s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      ]]>
 | 
					 | 
				
			||||||
    </style>
 | 
					 | 
				
			||||||
  </defs>
 | 
					 | 
				
			||||||
  <g transform="translate(16,-2)" filter="url(#blur)">
 | 
					 | 
				
			||||||
    <g transform="matrix(.8 0 0 .8 16 4)">
 | 
					 | 
				
			||||||
      <g class="am-weather-moon-star-1"
 | 
					 | 
				
			||||||
        style="-moz-animation-delay:3s;-moz-animation-duration:5s;-moz-animation-iteration-count:1;-moz-animation-name:am-weather-moon-star-1;-moz-animation-timing-function:linear;-ms-animation-delay:3s;-ms-animation-duration:5s;-ms-animation-iteration-count:1;-ms-animation-name:am-weather-moon-star-1;-ms-animation-timing-function:linear;-webkit-animation-delay:3s;-webkit-animation-duration:5s;-webkit-animation-iteration-count:1;-webkit-animation-name:am-weather-moon-star-1;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
        <polygon points="4 4 3.3 5.2 2.7 4 1.5 3.3 2.7 2.7 3.3 1.5 4 2.7 5.2 3.3" fill="#ffa500"
 | 
					 | 
				
			||||||
          stroke-miterlimit="10" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
      <g class="am-weather-moon-star-2"
 | 
					 | 
				
			||||||
        style="-moz-animation-delay:5s;-moz-animation-duration:4s;-moz-animation-iteration-count:1;-moz-animation-name:am-weather-moon-star-2;-moz-animation-timing-function:linear;-ms-animation-delay:5s;-ms-animation-duration:4s;-ms-animation-iteration-count:1;-ms-animation-name:am-weather-moon-star-2;-ms-animation-timing-function:linear;-webkit-animation-delay:5s;-webkit-animation-duration:4s;-webkit-animation-iteration-count:1;-webkit-animation-name:am-weather-moon-star-2;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
        <polygon transform="translate(20,10)" points="4 4 3.3 5.2 2.7 4 1.5 3.3 2.7 2.7 3.3 1.5 4 2.7 5.2 3.3"
 | 
					 | 
				
			||||||
          fill="#ffa500" stroke-miterlimit="10" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
      <g class="am-weather-moon"
 | 
					 | 
				
			||||||
        style="-moz-animation-duration:6s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-moon;-moz-animation-timing-function:linear;-moz-transform-origin:12.5px 15.15px 0;-ms-animation-duration:6s;-ms-animation-iteration-count:infinite;-ms-animation-name:am-weather-moon;-ms-animation-timing-function:linear;-ms-transform-origin:12.5px 15.15px 0;-webkit-animation-duration:6s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-moon;-webkit-animation-timing-function:linear;-webkit-transform-origin:12.5px 15.15px 0">
 | 
					 | 
				
			||||||
        <path
 | 
					 | 
				
			||||||
          d="m14.5 13.2c0-3.7 2-6.9 5-8.7-1.5-0.9-3.2-1.3-5-1.3-5.5 0-10 4.5-10 10s4.5 10 10 10c1.8 0 3.5-0.5 5-1.3-3-1.7-5-5-5-8.7z"
 | 
					 | 
				
			||||||
          fill="#ffa500" stroke="#ffa500" stroke-linejoin="round" stroke-width="2" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
    <g class="am-weather-cloud-3"
 | 
					 | 
				
			||||||
      style="-moz-animation-duration:3s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-cloud-2;-moz-animation-timing-function:linear;-webkit-animation-duration:3s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-cloud-2;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
      <path transform="translate(-20,-11)"
 | 
					 | 
				
			||||||
        d="m47.7 35.4c0-4.6-3.7-8.2-8.2-8.2-1 0-1.9 0.2-2.8 0.5-0.3-3.4-3.1-6.2-6.6-6.2-3.7 0-6.7 3-6.7 6.7 0 0.8 0.2 1.6 0.4 2.3-0.3-0.1-0.7-0.1-1-0.1-3.7 0-6.7 3-6.7 6.7 0 3.6 2.9 6.6 6.5 6.7h17.2c4.4-0.5 7.9-4 7.9-8.4z"
 | 
					 | 
				
			||||||
        fill="#57a0ee" stroke="#fff" stroke-linejoin="round" stroke-width="1.2" />
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
    <g class="am-weather-snow-1"
 | 
					 | 
				
			||||||
      style="-moz-animation-duration:2s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-snow;-moz-animation-timing-function:linear;-ms-animation-duration:2s;-ms-animation-iteration-count:infinite;-ms-animation-name:am-weather-snow;-ms-animation-timing-function:linear;-webkit-animation-duration:2s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-snow;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
      <g transform="translate(12,28)" fill="none" stroke="#57a0ee" stroke-linecap="round">
 | 
					 | 
				
			||||||
        <line transform="translate(0,9)" y1="-2.5" y2="2.5" stroke-width="1.2" />
 | 
					 | 
				
			||||||
        <line transform="rotate(45,-10.864,4.5)" y1="-2.5" y2="2.5" />
 | 
					 | 
				
			||||||
        <line transform="rotate(90,-4.5,4.5)" y1="-2.5" y2="2.5" />
 | 
					 | 
				
			||||||
        <line transform="rotate(135,-1.864,4.5)" y1="-2.5" y2="2.5" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
  </g>
 | 
					 | 
				
			||||||
</svg>
 | 
					 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 11 KiB  | 
@@ -1,273 +0,0 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
					 | 
				
			||||||
<!-- (c) ammap.com | SVG weather icons -->
 | 
					 | 
				
			||||||
<svg width="56" height="48" version="1.1" xmlns="http://www.w3.org/2000/svg">
 | 
					 | 
				
			||||||
  <defs>
 | 
					 | 
				
			||||||
    <filter id="blur" x="-.20655" y="-.23099" width="1.403" height="1.5634">
 | 
					 | 
				
			||||||
      <feGaussianBlur in="SourceAlpha" stdDeviation="3" />
 | 
					 | 
				
			||||||
      <feOffset dx="0" dy="4" result="offsetblur" />
 | 
					 | 
				
			||||||
      <feComponentTransfer>
 | 
					 | 
				
			||||||
        <feFuncA slope="0.05" type="linear" />
 | 
					 | 
				
			||||||
      </feComponentTransfer>
 | 
					 | 
				
			||||||
      <feMerge>
 | 
					 | 
				
			||||||
        <feMergeNode />
 | 
					 | 
				
			||||||
        <feMergeNode in="SourceGraphic" />
 | 
					 | 
				
			||||||
      </feMerge>
 | 
					 | 
				
			||||||
    </filter>
 | 
					 | 
				
			||||||
    <style type="text/css">
 | 
					 | 
				
			||||||
      <![CDATA[
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** CLOUDS
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-cloud-2 {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        50% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-cloud-2 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-cloud-2;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-cloud-2;
 | 
					 | 
				
			||||||
        animation-name: am-weather-cloud-2;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 3s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 3s;
 | 
					 | 
				
			||||||
        animation-duration: 3s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** SUN
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-sun {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          -webkit-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -moz-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -ms-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          transform: rotate(0deg);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          -webkit-transform: rotate(360deg);
 | 
					 | 
				
			||||||
          -moz-transform: rotate(360deg);
 | 
					 | 
				
			||||||
          -ms-transform: rotate(360deg);
 | 
					 | 
				
			||||||
          transform: rotate(360deg);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-sun {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-sun;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-sun;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-sun;
 | 
					 | 
				
			||||||
        animation-name: am-weather-sun;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 9s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 9s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 9s;
 | 
					 | 
				
			||||||
        animation-duration: 9s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      @keyframes am-weather-sun-shiny {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          stroke-dasharray: 3px 10px;
 | 
					 | 
				
			||||||
          stroke-dashoffset: 0px;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        50% {
 | 
					 | 
				
			||||||
          stroke-dasharray: 0.1px 10px;
 | 
					 | 
				
			||||||
          stroke-dashoffset: -1px;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          stroke-dasharray: 3px 10px;
 | 
					 | 
				
			||||||
          stroke-dashoffset: 0px;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-sun-shiny line {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-sun-shiny;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-sun-shiny;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-sun-shiny;
 | 
					 | 
				
			||||||
        animation-name: am-weather-sun-shiny;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 2s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 2s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 2s;
 | 
					 | 
				
			||||||
        animation-duration: 2s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** SNOW
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-snow {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          -webkit-transform: translateX(0) translateY(0);
 | 
					 | 
				
			||||||
          -moz-transform: translateX(0) translateY(0);
 | 
					 | 
				
			||||||
          -ms-transform: translateX(0) translateY(0);
 | 
					 | 
				
			||||||
          transform: translateX(0) translateY(0);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        33.33% {
 | 
					 | 
				
			||||||
          -webkit-transform: translateX(-1.2px) translateY(2px);
 | 
					 | 
				
			||||||
          -moz-transform: translateX(-1.2px) translateY(2px);
 | 
					 | 
				
			||||||
          -ms-transform: translateX(-1.2px) translateY(2px);
 | 
					 | 
				
			||||||
          transform: translateX(-1.2px) translateY(2px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        66.66% {
 | 
					 | 
				
			||||||
          -webkit-transform: translateX(1.4px) translateY(4px);
 | 
					 | 
				
			||||||
          -moz-transform: translateX(1.4px) translateY(4px);
 | 
					 | 
				
			||||||
          -ms-transform: translateX(1.4px) translateY(4px);
 | 
					 | 
				
			||||||
          transform: translateX(1.4px) translateY(4px);
 | 
					 | 
				
			||||||
          opacity: 1;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          -webkit-transform: translateX(-1.6px) translateY(6px);
 | 
					 | 
				
			||||||
          -moz-transform: translateX(-1.6px) translateY(6px);
 | 
					 | 
				
			||||||
          -ms-transform: translateX(-1.6px) translateY(6px);
 | 
					 | 
				
			||||||
          transform: translateX(-1.6px) translateY(6px);
 | 
					 | 
				
			||||||
          opacity: 0;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-snow-1 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-snow;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-snow;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-snow;
 | 
					 | 
				
			||||||
        animation-name: am-weather-snow;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 2s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 2s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 2s;
 | 
					 | 
				
			||||||
        animation-duration: 2s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-snow-2 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-snow;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-snow;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-snow;
 | 
					 | 
				
			||||||
        animation-name: am-weather-snow;
 | 
					 | 
				
			||||||
        -webkit-animation-delay: 1.2s;
 | 
					 | 
				
			||||||
        -moz-animation-delay: 1.2s;
 | 
					 | 
				
			||||||
        -ms-animation-delay: 1.2s;
 | 
					 | 
				
			||||||
        animation-delay: 1.2s;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 2s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 2s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 2s;
 | 
					 | 
				
			||||||
        animation-duration: 2s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      ]]>
 | 
					 | 
				
			||||||
    </style>
 | 
					 | 
				
			||||||
  </defs>
 | 
					 | 
				
			||||||
  <g transform="translate(16,-2)" filter="url(#blur)">
 | 
					 | 
				
			||||||
    <g transform="translate(0,16)">
 | 
					 | 
				
			||||||
      <g class="am-weather-sun"
 | 
					 | 
				
			||||||
        style="-moz-animation-duration:9s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-sun;-moz-animation-timing-function:linear;-ms-animation-duration:9s;-ms-animation-iteration-count:infinite;-ms-animation-name:am-weather-sun;-ms-animation-timing-function:linear;-webkit-animation-duration:9s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-sun;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
        <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round" stroke-width="2" />
 | 
					 | 
				
			||||||
        <g transform="rotate(45)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="rotate(90)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="rotate(135)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="scale(-1)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="rotate(225)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="rotate(-90)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
        <g transform="rotate(-45)">
 | 
					 | 
				
			||||||
          <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
            stroke-width="2" />
 | 
					 | 
				
			||||||
        </g>
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
      <circle r="5" fill="#ffa500" stroke="#ffa500" stroke-width="2" />
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
    <g class="am-weather-cloud-3"
 | 
					 | 
				
			||||||
      style="-moz-animation-duration:3s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-cloud-2;-moz-animation-timing-function:linear;-webkit-animation-duration:3s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-cloud-2;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
      <path transform="translate(-20,-11)"
 | 
					 | 
				
			||||||
        d="m47.7 35.4c0-4.6-3.7-8.2-8.2-8.2-1 0-1.9 0.2-2.8 0.5-0.3-3.4-3.1-6.2-6.6-6.2-3.7 0-6.7 3-6.7 6.7 0 0.8 0.2 1.6 0.4 2.3-0.3-0.1-0.7-0.1-1-0.1-3.7 0-6.7 3-6.7 6.7 0 3.6 2.9 6.6 6.5 6.7h17.2c4.4-0.5 7.9-4 7.9-8.4z"
 | 
					 | 
				
			||||||
        fill="#57a0ee" stroke="#fff" stroke-linejoin="round" stroke-width="1.2" />
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
    <g class="am-weather-snow-1"
 | 
					 | 
				
			||||||
      style="-moz-animation-duration:2s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-snow;-moz-animation-timing-function:linear;-ms-animation-duration:2s;-ms-animation-iteration-count:infinite;-ms-animation-name:am-weather-snow;-ms-animation-timing-function:linear;-webkit-animation-duration:2s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-snow;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
      <g transform="translate(7,28)" fill="none" stroke="#57a0ee" stroke-linecap="round">
 | 
					 | 
				
			||||||
        <line transform="translate(0,9)" y1="-2.5" y2="2.5" stroke-width="1.2" />
 | 
					 | 
				
			||||||
        <line transform="rotate(45,-10.864,4.5)" y1="-2.5" y2="2.5" />
 | 
					 | 
				
			||||||
        <line transform="rotate(90,-4.5,4.5)" y1="-2.5" y2="2.5" />
 | 
					 | 
				
			||||||
        <line transform="rotate(135,-1.864,4.5)" y1="-2.5" y2="2.5" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
    <g class="am-weather-snow-2"
 | 
					 | 
				
			||||||
      style="-moz-animation-delay:1.2s;-moz-animation-duration:2s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-snow;-moz-animation-timing-function:linear;-ms-animation-delay:1.2s;-ms-animation-duration:2s;-ms-animation-iteration-count:infinite;-ms-animation-name:am-weather-snow;-ms-animation-timing-function:linear;-webkit-animation-delay:1.2s;-webkit-animation-duration:2s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-snow;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
      <g transform="translate(16,28)" fill="none" stroke="#57a0ee" stroke-linecap="round">
 | 
					 | 
				
			||||||
        <line transform="translate(0,9)" y1="-2.5" y2="2.5" stroke-width="1.2" />
 | 
					 | 
				
			||||||
        <line transform="rotate(45,-10.864,4.5)" y1="-2.5" y2="2.5" />
 | 
					 | 
				
			||||||
        <line transform="rotate(90,-4.5,4.5)" y1="-2.5" y2="2.5" />
 | 
					 | 
				
			||||||
        <line transform="rotate(135,-1.864,4.5)" y1="-2.5" y2="2.5" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
  </g>
 | 
					 | 
				
			||||||
</svg>
 | 
					 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 11 KiB  | 
@@ -1,301 +0,0 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
					 | 
				
			||||||
<!-- (c) ammap.com | SVG weather icons -->
 | 
					 | 
				
			||||||
<svg width="56" height="48" version="1.1" xmlns="http://www.w3.org/2000/svg">
 | 
					 | 
				
			||||||
  <defs>
 | 
					 | 
				
			||||||
    <filter id="blur" x="-.20655" y="-.23099" width="1.403" height="1.5634">
 | 
					 | 
				
			||||||
      <feGaussianBlur in="SourceAlpha" stdDeviation="3" />
 | 
					 | 
				
			||||||
      <feOffset dx="0" dy="4" result="offsetblur" />
 | 
					 | 
				
			||||||
      <feComponentTransfer>
 | 
					 | 
				
			||||||
        <feFuncA slope="0.05" type="linear" />
 | 
					 | 
				
			||||||
      </feComponentTransfer>
 | 
					 | 
				
			||||||
      <feMerge>
 | 
					 | 
				
			||||||
        <feMergeNode />
 | 
					 | 
				
			||||||
        <feMergeNode in="SourceGraphic" />
 | 
					 | 
				
			||||||
      </feMerge>
 | 
					 | 
				
			||||||
    </filter>
 | 
					 | 
				
			||||||
    <style type="text/css">
 | 
					 | 
				
			||||||
      <![CDATA[
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** CLOUDS
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-cloud-2 {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        50% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          -webkit-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -moz-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          -ms-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
          transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-cloud-2 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-cloud-2;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-cloud-2;
 | 
					 | 
				
			||||||
        animation-name: am-weather-cloud-2;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 3s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 3s;
 | 
					 | 
				
			||||||
        animation-duration: 3s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** MOON
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-moon {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          -webkit-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -moz-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -ms-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          transform: rotate(0deg);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        50% {
 | 
					 | 
				
			||||||
          -webkit-transform: rotate(15deg);
 | 
					 | 
				
			||||||
          -moz-transform: rotate(15deg);
 | 
					 | 
				
			||||||
          -ms-transform: rotate(15deg);
 | 
					 | 
				
			||||||
          transform: rotate(15deg);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          -webkit-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -moz-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          -ms-transform: rotate(0deg);
 | 
					 | 
				
			||||||
          transform: rotate(0deg);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-moon {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-moon;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-moon;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-moon;
 | 
					 | 
				
			||||||
        animation-name: am-weather-moon;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 6s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 6s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 6s;
 | 
					 | 
				
			||||||
        animation-duration: 6s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -webkit-transform-origin: 12.5px 15.15px 0;
 | 
					 | 
				
			||||||
        /* TODO FF CENTER ISSUE */
 | 
					 | 
				
			||||||
        -moz-transform-origin: 12.5px 15.15px 0;
 | 
					 | 
				
			||||||
        /* TODO FF CENTER ISSUE */
 | 
					 | 
				
			||||||
        -ms-transform-origin: 12.5px 15.15px 0;
 | 
					 | 
				
			||||||
        /* TODO FF CENTER ISSUE */
 | 
					 | 
				
			||||||
        transform-origin: 12.5px 15.15px 0;
 | 
					 | 
				
			||||||
        /* TODO FF CENTER ISSUE */
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      @keyframes am-weather-moon-star-1 {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          opacity: 0;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          opacity: 1;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-moon-star-1 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-moon-star-1;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-moon-star-1;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-moon-star-1;
 | 
					 | 
				
			||||||
        animation-name: am-weather-moon-star-1;
 | 
					 | 
				
			||||||
        -webkit-animation-delay: 3s;
 | 
					 | 
				
			||||||
        -moz-animation-delay: 3s;
 | 
					 | 
				
			||||||
        -ms-animation-delay: 3s;
 | 
					 | 
				
			||||||
        animation-delay: 3s;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 5s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 5s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 5s;
 | 
					 | 
				
			||||||
        animation-duration: 5s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        animation-iteration-count: 1;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      @keyframes am-weather-moon-star-2 {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          opacity: 0;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          opacity: 1;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-moon-star-2 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-moon-star-2;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-moon-star-2;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-moon-star-2;
 | 
					 | 
				
			||||||
        animation-name: am-weather-moon-star-2;
 | 
					 | 
				
			||||||
        -webkit-animation-delay: 5s;
 | 
					 | 
				
			||||||
        -moz-animation-delay: 5s;
 | 
					 | 
				
			||||||
        -ms-animation-delay: 5s;
 | 
					 | 
				
			||||||
        animation-delay: 5s;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 4s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 4s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 4s;
 | 
					 | 
				
			||||||
        animation-duration: 4s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
        animation-iteration-count: 1;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
** SNOW
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
      @keyframes am-weather-snow {
 | 
					 | 
				
			||||||
        0% {
 | 
					 | 
				
			||||||
          -webkit-transform: translateX(0) translateY(0);
 | 
					 | 
				
			||||||
          -moz-transform: translateX(0) translateY(0);
 | 
					 | 
				
			||||||
          -ms-transform: translateX(0) translateY(0);
 | 
					 | 
				
			||||||
          transform: translateX(0) translateY(0);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        33.33% {
 | 
					 | 
				
			||||||
          -webkit-transform: translateX(-1.2px) translateY(2px);
 | 
					 | 
				
			||||||
          -moz-transform: translateX(-1.2px) translateY(2px);
 | 
					 | 
				
			||||||
          -ms-transform: translateX(-1.2px) translateY(2px);
 | 
					 | 
				
			||||||
          transform: translateX(-1.2px) translateY(2px);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        66.66% {
 | 
					 | 
				
			||||||
          -webkit-transform: translateX(1.4px) translateY(4px);
 | 
					 | 
				
			||||||
          -moz-transform: translateX(1.4px) translateY(4px);
 | 
					 | 
				
			||||||
          -ms-transform: translateX(1.4px) translateY(4px);
 | 
					 | 
				
			||||||
          transform: translateX(1.4px) translateY(4px);
 | 
					 | 
				
			||||||
          opacity: 1;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        100% {
 | 
					 | 
				
			||||||
          -webkit-transform: translateX(-1.6px) translateY(6px);
 | 
					 | 
				
			||||||
          -moz-transform: translateX(-1.6px) translateY(6px);
 | 
					 | 
				
			||||||
          -ms-transform: translateX(-1.6px) translateY(6px);
 | 
					 | 
				
			||||||
          transform: translateX(-1.6px) translateY(6px);
 | 
					 | 
				
			||||||
          opacity: 0;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-snow-1 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-snow;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-snow;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-snow;
 | 
					 | 
				
			||||||
        animation-name: am-weather-snow;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 2s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 2s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 2s;
 | 
					 | 
				
			||||||
        animation-duration: 2s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      .am-weather-snow-2 {
 | 
					 | 
				
			||||||
        -webkit-animation-name: am-weather-snow;
 | 
					 | 
				
			||||||
        -moz-animation-name: am-weather-snow;
 | 
					 | 
				
			||||||
        -ms-animation-name: am-weather-snow;
 | 
					 | 
				
			||||||
        animation-name: am-weather-snow;
 | 
					 | 
				
			||||||
        -webkit-animation-delay: 1.2s;
 | 
					 | 
				
			||||||
        -moz-animation-delay: 1.2s;
 | 
					 | 
				
			||||||
        -ms-animation-delay: 1.2s;
 | 
					 | 
				
			||||||
        animation-delay: 1.2s;
 | 
					 | 
				
			||||||
        -webkit-animation-duration: 2s;
 | 
					 | 
				
			||||||
        -moz-animation-duration: 2s;
 | 
					 | 
				
			||||||
        -ms-animation-duration: 2s;
 | 
					 | 
				
			||||||
        animation-duration: 2s;
 | 
					 | 
				
			||||||
        -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
        animation-timing-function: linear;
 | 
					 | 
				
			||||||
        -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
        animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      ]]>
 | 
					 | 
				
			||||||
    </style>
 | 
					 | 
				
			||||||
  </defs>
 | 
					 | 
				
			||||||
  <g transform="translate(16,-2)" filter="url(#blur)">
 | 
					 | 
				
			||||||
    <g transform="matrix(.8 0 0 .8 16 4)">
 | 
					 | 
				
			||||||
      <g class="am-weather-moon-star-1"
 | 
					 | 
				
			||||||
        style="-moz-animation-delay:3s;-moz-animation-duration:5s;-moz-animation-iteration-count:1;-moz-animation-name:am-weather-moon-star-1;-moz-animation-timing-function:linear;-ms-animation-delay:3s;-ms-animation-duration:5s;-ms-animation-iteration-count:1;-ms-animation-name:am-weather-moon-star-1;-ms-animation-timing-function:linear;-webkit-animation-delay:3s;-webkit-animation-duration:5s;-webkit-animation-iteration-count:1;-webkit-animation-name:am-weather-moon-star-1;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
        <polygon points="4 4 3.3 5.2 2.7 4 1.5 3.3 2.7 2.7 3.3 1.5 4 2.7 5.2 3.3" fill="#ffa500"
 | 
					 | 
				
			||||||
          stroke-miterlimit="10" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
      <g class="am-weather-moon-star-2"
 | 
					 | 
				
			||||||
        style="-moz-animation-delay:5s;-moz-animation-duration:4s;-moz-animation-iteration-count:1;-moz-animation-name:am-weather-moon-star-2;-moz-animation-timing-function:linear;-ms-animation-delay:5s;-ms-animation-duration:4s;-ms-animation-iteration-count:1;-ms-animation-name:am-weather-moon-star-2;-ms-animation-timing-function:linear;-webkit-animation-delay:5s;-webkit-animation-duration:4s;-webkit-animation-iteration-count:1;-webkit-animation-name:am-weather-moon-star-2;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
        <polygon transform="translate(20,10)" points="4 4 3.3 5.2 2.7 4 1.5 3.3 2.7 2.7 3.3 1.5 4 2.7 5.2 3.3"
 | 
					 | 
				
			||||||
          fill="#ffa500" stroke-miterlimit="10" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
      <g class="am-weather-moon"
 | 
					 | 
				
			||||||
        style="-moz-animation-duration:6s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-moon;-moz-animation-timing-function:linear;-moz-transform-origin:12.5px 15.15px 0;-ms-animation-duration:6s;-ms-animation-iteration-count:infinite;-ms-animation-name:am-weather-moon;-ms-animation-timing-function:linear;-ms-transform-origin:12.5px 15.15px 0;-webkit-animation-duration:6s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-moon;-webkit-animation-timing-function:linear;-webkit-transform-origin:12.5px 15.15px 0">
 | 
					 | 
				
			||||||
        <path
 | 
					 | 
				
			||||||
          d="m14.5 13.2c0-3.7 2-6.9 5-8.7-1.5-0.9-3.2-1.3-5-1.3-5.5 0-10 4.5-10 10s4.5 10 10 10c1.8 0 3.5-0.5 5-1.3-3-1.7-5-5-5-8.7z"
 | 
					 | 
				
			||||||
          fill="#ffa500" stroke="#ffa500" stroke-linejoin="round" stroke-width="2" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
    <g class="am-weather-cloud-3"
 | 
					 | 
				
			||||||
      style="-moz-animation-duration:3s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-cloud-2;-moz-animation-timing-function:linear;-webkit-animation-duration:3s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-cloud-2;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
      <path transform="translate(-20,-11)"
 | 
					 | 
				
			||||||
        d="m47.7 35.4c0-4.6-3.7-8.2-8.2-8.2-1 0-1.9 0.2-2.8 0.5-0.3-3.4-3.1-6.2-6.6-6.2-3.7 0-6.7 3-6.7 6.7 0 0.8 0.2 1.6 0.4 2.3-0.3-0.1-0.7-0.1-1-0.1-3.7 0-6.7 3-6.7 6.7 0 3.6 2.9 6.6 6.5 6.7h17.2c4.4-0.5 7.9-4 7.9-8.4z"
 | 
					 | 
				
			||||||
        fill="#57a0ee" stroke="#fff" stroke-linejoin="round" stroke-width="1.2" />
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
    <g class="am-weather-snow-1"
 | 
					 | 
				
			||||||
      style="-moz-animation-duration:2s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-snow;-moz-animation-timing-function:linear;-ms-animation-duration:2s;-ms-animation-iteration-count:infinite;-ms-animation-name:am-weather-snow;-ms-animation-timing-function:linear;-webkit-animation-duration:2s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-snow;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
      <g transform="translate(7,28)" fill="none" stroke="#57a0ee" stroke-linecap="round">
 | 
					 | 
				
			||||||
        <line transform="translate(0,9)" y1="-2.5" y2="2.5" stroke-width="1.2" />
 | 
					 | 
				
			||||||
        <line transform="rotate(45,-10.864,4.5)" y1="-2.5" y2="2.5" />
 | 
					 | 
				
			||||||
        <line transform="rotate(90,-4.5,4.5)" y1="-2.5" y2="2.5" />
 | 
					 | 
				
			||||||
        <line transform="rotate(135,-1.864,4.5)" y1="-2.5" y2="2.5" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
    <g class="am-weather-snow-2"
 | 
					 | 
				
			||||||
      style="-moz-animation-delay:1.2s;-moz-animation-duration:2s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-snow;-moz-animation-timing-function:linear;-ms-animation-delay:1.2s;-ms-animation-duration:2s;-ms-animation-iteration-count:infinite;-ms-animation-name:am-weather-snow;-ms-animation-timing-function:linear;-webkit-animation-delay:1.2s;-webkit-animation-duration:2s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-snow;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
      <g transform="translate(16,28)" fill="none" stroke="#57a0ee" stroke-linecap="round">
 | 
					 | 
				
			||||||
        <line transform="translate(0,9)" y1="-2.5" y2="2.5" stroke-width="1.2" />
 | 
					 | 
				
			||||||
        <line transform="rotate(45,-10.864,4.5)" y1="-2.5" y2="2.5" />
 | 
					 | 
				
			||||||
        <line transform="rotate(90,-4.5,4.5)" y1="-2.5" y2="2.5" />
 | 
					 | 
				
			||||||
        <line transform="rotate(135,-1.864,4.5)" y1="-2.5" y2="2.5" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
    </g>
 | 
					 | 
				
			||||||
  </g>
 | 
					 | 
				
			||||||
</svg>
 | 
					 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 13 KiB  | 
@@ -1,334 +0,0 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
					 | 
				
			||||||
<!-- (c) ammap.com | SVG weather icons -->
 | 
					 | 
				
			||||||
<svg width="56" height="48" version="1.1" xmlns="http://www.w3.org/2000/svg">
 | 
					 | 
				
			||||||
   <defs>
 | 
					 | 
				
			||||||
      <filter id="blur" x="-.24684" y="-.26897" width="1.4937" height="1.6759">
 | 
					 | 
				
			||||||
         <feGaussianBlur in="SourceAlpha" stdDeviation="3" />
 | 
					 | 
				
			||||||
         <feOffset dx="0" dy="4" result="offsetblur" />
 | 
					 | 
				
			||||||
         <feComponentTransfer>
 | 
					 | 
				
			||||||
            <feFuncA slope="0.05" type="linear" />
 | 
					 | 
				
			||||||
         </feComponentTransfer>
 | 
					 | 
				
			||||||
         <feMerge>
 | 
					 | 
				
			||||||
            <feMergeNode />
 | 
					 | 
				
			||||||
            <feMergeNode in="SourceGraphic" />
 | 
					 | 
				
			||||||
         </feMerge>
 | 
					 | 
				
			||||||
      </filter>
 | 
					 | 
				
			||||||
      <style type="text/css">
 | 
					 | 
				
			||||||
         <![CDATA[
 | 
					 | 
				
			||||||
         /*
 | 
					 | 
				
			||||||
** CLOUDS
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
         @keyframes am-weather-cloud-2 {
 | 
					 | 
				
			||||||
            0% {
 | 
					 | 
				
			||||||
               -webkit-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
               -moz-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
               -ms-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
               transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            50% {
 | 
					 | 
				
			||||||
               -webkit-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
               -moz-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
               -ms-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
               transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            100% {
 | 
					 | 
				
			||||||
               -webkit-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
               -moz-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
               -ms-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
               transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
         }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
         .am-weather-cloud-2 {
 | 
					 | 
				
			||||||
            -webkit-animation-name: am-weather-cloud-2;
 | 
					 | 
				
			||||||
            -moz-animation-name: am-weather-cloud-2;
 | 
					 | 
				
			||||||
            animation-name: am-weather-cloud-2;
 | 
					 | 
				
			||||||
            -webkit-animation-duration: 3s;
 | 
					 | 
				
			||||||
            -moz-animation-duration: 3s;
 | 
					 | 
				
			||||||
            animation-duration: 3s;
 | 
					 | 
				
			||||||
            -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
            -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
            animation-timing-function: linear;
 | 
					 | 
				
			||||||
            -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
            -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
            animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
         }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
         /*
 | 
					 | 
				
			||||||
** SUN
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
         @keyframes am-weather-sun {
 | 
					 | 
				
			||||||
            0% {
 | 
					 | 
				
			||||||
               -webkit-transform: rotate(0deg);
 | 
					 | 
				
			||||||
               -moz-transform: rotate(0deg);
 | 
					 | 
				
			||||||
               -ms-transform: rotate(0deg);
 | 
					 | 
				
			||||||
               transform: rotate(0deg);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            100% {
 | 
					 | 
				
			||||||
               -webkit-transform: rotate(360deg);
 | 
					 | 
				
			||||||
               -moz-transform: rotate(360deg);
 | 
					 | 
				
			||||||
               -ms-transform: rotate(360deg);
 | 
					 | 
				
			||||||
               transform: rotate(360deg);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
         }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
         .am-weather-sun {
 | 
					 | 
				
			||||||
            -webkit-animation-name: am-weather-sun;
 | 
					 | 
				
			||||||
            -moz-animation-name: am-weather-sun;
 | 
					 | 
				
			||||||
            -ms-animation-name: am-weather-sun;
 | 
					 | 
				
			||||||
            animation-name: am-weather-sun;
 | 
					 | 
				
			||||||
            -webkit-animation-duration: 9s;
 | 
					 | 
				
			||||||
            -moz-animation-duration: 9s;
 | 
					 | 
				
			||||||
            -ms-animation-duration: 9s;
 | 
					 | 
				
			||||||
            animation-duration: 9s;
 | 
					 | 
				
			||||||
            -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
            -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
            -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
            animation-timing-function: linear;
 | 
					 | 
				
			||||||
            -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
            -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
            -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
            animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
         }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
         @keyframes am-weather-sun-shiny {
 | 
					 | 
				
			||||||
            0% {
 | 
					 | 
				
			||||||
               stroke-dasharray: 3px 10px;
 | 
					 | 
				
			||||||
               stroke-dashoffset: 0px;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            50% {
 | 
					 | 
				
			||||||
               stroke-dasharray: 0.1px 10px;
 | 
					 | 
				
			||||||
               stroke-dashoffset: -1px;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            100% {
 | 
					 | 
				
			||||||
               stroke-dasharray: 3px 10px;
 | 
					 | 
				
			||||||
               stroke-dashoffset: 0px;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
         }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
         .am-weather-sun-shiny line {
 | 
					 | 
				
			||||||
            -webkit-animation-name: am-weather-sun-shiny;
 | 
					 | 
				
			||||||
            -moz-animation-name: am-weather-sun-shiny;
 | 
					 | 
				
			||||||
            -ms-animation-name: am-weather-sun-shiny;
 | 
					 | 
				
			||||||
            animation-name: am-weather-sun-shiny;
 | 
					 | 
				
			||||||
            -webkit-animation-duration: 2s;
 | 
					 | 
				
			||||||
            -moz-animation-duration: 2s;
 | 
					 | 
				
			||||||
            -ms-animation-duration: 2s;
 | 
					 | 
				
			||||||
            animation-duration: 2s;
 | 
					 | 
				
			||||||
            -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
            -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
            -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
            animation-timing-function: linear;
 | 
					 | 
				
			||||||
            -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
            -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
            -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
            animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
         }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
         /*
 | 
					 | 
				
			||||||
** SNOW
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
         @keyframes am-weather-snow {
 | 
					 | 
				
			||||||
            0% {
 | 
					 | 
				
			||||||
               -webkit-transform: translateX(0) translateY(0);
 | 
					 | 
				
			||||||
               -moz-transform: translateX(0) translateY(0);
 | 
					 | 
				
			||||||
               -ms-transform: translateX(0) translateY(0);
 | 
					 | 
				
			||||||
               transform: translateX(0) translateY(0);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            33.33% {
 | 
					 | 
				
			||||||
               -webkit-transform: translateX(-1.2px) translateY(2px);
 | 
					 | 
				
			||||||
               -moz-transform: translateX(-1.2px) translateY(2px);
 | 
					 | 
				
			||||||
               -ms-transform: translateX(-1.2px) translateY(2px);
 | 
					 | 
				
			||||||
               transform: translateX(-1.2px) translateY(2px);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            66.66% {
 | 
					 | 
				
			||||||
               -webkit-transform: translateX(1.4px) translateY(4px);
 | 
					 | 
				
			||||||
               -moz-transform: translateX(1.4px) translateY(4px);
 | 
					 | 
				
			||||||
               -ms-transform: translateX(1.4px) translateY(4px);
 | 
					 | 
				
			||||||
               transform: translateX(1.4px) translateY(4px);
 | 
					 | 
				
			||||||
               opacity: 1;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            100% {
 | 
					 | 
				
			||||||
               -webkit-transform: translateX(-1.6px) translateY(6px);
 | 
					 | 
				
			||||||
               -moz-transform: translateX(-1.6px) translateY(6px);
 | 
					 | 
				
			||||||
               -ms-transform: translateX(-1.6px) translateY(6px);
 | 
					 | 
				
			||||||
               transform: translateX(-1.6px) translateY(6px);
 | 
					 | 
				
			||||||
               opacity: 0;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
         }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
         @keyframes am-weather-snow-reverse {
 | 
					 | 
				
			||||||
            0% {
 | 
					 | 
				
			||||||
               -webkit-transform: translateX(0) translateY(0);
 | 
					 | 
				
			||||||
               -moz-transform: translateX(0) translateY(0);
 | 
					 | 
				
			||||||
               -ms-transform: translateX(0) translateY(0);
 | 
					 | 
				
			||||||
               transform: translateX(0) translateY(0);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            33.33% {
 | 
					 | 
				
			||||||
               -webkit-transform: translateX(1.2px) translateY(2px);
 | 
					 | 
				
			||||||
               -moz-transform: translateX(1.2px) translateY(2px);
 | 
					 | 
				
			||||||
               -ms-transform: translateX(1.2px) translateY(2px);
 | 
					 | 
				
			||||||
               transform: translateX(1.2px) translateY(2px);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            66.66% {
 | 
					 | 
				
			||||||
               -webkit-transform: translateX(-1.4px) translateY(4px);
 | 
					 | 
				
			||||||
               -moz-transform: translateX(-1.4px) translateY(4px);
 | 
					 | 
				
			||||||
               -ms-transform: translateX(-1.4px) translateY(4px);
 | 
					 | 
				
			||||||
               transform: translateX(-1.4px) translateY(4px);
 | 
					 | 
				
			||||||
               opacity: 1;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            100% {
 | 
					 | 
				
			||||||
               -webkit-transform: translateX(1.6px) translateY(6px);
 | 
					 | 
				
			||||||
               -moz-transform: translateX(1.6px) translateY(6px);
 | 
					 | 
				
			||||||
               -ms-transform: translateX(1.6px) translateY(6px);
 | 
					 | 
				
			||||||
               transform: translateX(1.6px) translateY(6px);
 | 
					 | 
				
			||||||
               opacity: 0;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
         }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
         .am-weather-snow-1 {
 | 
					 | 
				
			||||||
            -webkit-animation-name: am-weather-snow;
 | 
					 | 
				
			||||||
            -moz-animation-name: am-weather-snow;
 | 
					 | 
				
			||||||
            -ms-animation-name: am-weather-snow;
 | 
					 | 
				
			||||||
            animation-name: am-weather-snow;
 | 
					 | 
				
			||||||
            -webkit-animation-duration: 2s;
 | 
					 | 
				
			||||||
            -moz-animation-duration: 2s;
 | 
					 | 
				
			||||||
            -ms-animation-duration: 2s;
 | 
					 | 
				
			||||||
            animation-duration: 2s;
 | 
					 | 
				
			||||||
            -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
            -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
            -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
            animation-timing-function: linear;
 | 
					 | 
				
			||||||
            -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
            -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
            -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
            animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
         }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
         .am-weather-snow-2 {
 | 
					 | 
				
			||||||
            -webkit-animation-name: am-weather-snow;
 | 
					 | 
				
			||||||
            -moz-animation-name: am-weather-snow;
 | 
					 | 
				
			||||||
            -ms-animation-name: am-weather-snow;
 | 
					 | 
				
			||||||
            animation-name: am-weather-snow;
 | 
					 | 
				
			||||||
            -webkit-animation-delay: 1.2s;
 | 
					 | 
				
			||||||
            -moz-animation-delay: 1.2s;
 | 
					 | 
				
			||||||
            -ms-animation-delay: 1.2s;
 | 
					 | 
				
			||||||
            animation-delay: 1.2s;
 | 
					 | 
				
			||||||
            -webkit-animation-duration: 2s;
 | 
					 | 
				
			||||||
            -moz-animation-duration: 2s;
 | 
					 | 
				
			||||||
            -ms-animation-duration: 2s;
 | 
					 | 
				
			||||||
            animation-duration: 2s;
 | 
					 | 
				
			||||||
            -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
            -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
            -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
            animation-timing-function: linear;
 | 
					 | 
				
			||||||
            -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
            -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
            -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
            animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
         }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
         .am-weather-snow-3 {
 | 
					 | 
				
			||||||
            -webkit-animation-name: am-weather-snow-reverse;
 | 
					 | 
				
			||||||
            -moz-animation-name: am-weather-snow-reverse;
 | 
					 | 
				
			||||||
            -ms-animation-name: am-weather-snow-reverse;
 | 
					 | 
				
			||||||
            animation-name: am-weather-snow-reverse;
 | 
					 | 
				
			||||||
            -webkit-animation-duration: 2s;
 | 
					 | 
				
			||||||
            -moz-animation-duration: 2s;
 | 
					 | 
				
			||||||
            -ms-animation-duration: 2s;
 | 
					 | 
				
			||||||
            animation-duration: 2s;
 | 
					 | 
				
			||||||
            -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
            -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
            -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
            animation-timing-function: linear;
 | 
					 | 
				
			||||||
            -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
            -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
            -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
            animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
         }
 | 
					 | 
				
			||||||
         ]]>
 | 
					 | 
				
			||||||
      </style>
 | 
					 | 
				
			||||||
   </defs>
 | 
					 | 
				
			||||||
   <g transform="translate(16,-2)" filter="url(#blur)">
 | 
					 | 
				
			||||||
      <g transform="translate(0,16)">
 | 
					 | 
				
			||||||
         <g class="am-weather-sun"
 | 
					 | 
				
			||||||
            style="-moz-animation-duration:9s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-sun;-moz-animation-timing-function:linear;-ms-animation-duration:9s;-ms-animation-iteration-count:infinite;-ms-animation-name:am-weather-sun;-ms-animation-timing-function:linear;-webkit-animation-duration:9s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-sun;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
            <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
               stroke-width="2" />
 | 
					 | 
				
			||||||
            <g transform="rotate(45)">
 | 
					 | 
				
			||||||
               <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
                  stroke-width="2" />
 | 
					 | 
				
			||||||
            </g>
 | 
					 | 
				
			||||||
            <g transform="rotate(90)">
 | 
					 | 
				
			||||||
               <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
                  stroke-width="2" />
 | 
					 | 
				
			||||||
            </g>
 | 
					 | 
				
			||||||
            <g transform="rotate(135)">
 | 
					 | 
				
			||||||
               <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
                  stroke-width="2" />
 | 
					 | 
				
			||||||
            </g>
 | 
					 | 
				
			||||||
            <g transform="scale(-1)">
 | 
					 | 
				
			||||||
               <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
                  stroke-width="2" />
 | 
					 | 
				
			||||||
            </g>
 | 
					 | 
				
			||||||
            <g transform="rotate(225)">
 | 
					 | 
				
			||||||
               <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
                  stroke-width="2" />
 | 
					 | 
				
			||||||
            </g>
 | 
					 | 
				
			||||||
            <g transform="rotate(-90)">
 | 
					 | 
				
			||||||
               <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
                  stroke-width="2" />
 | 
					 | 
				
			||||||
            </g>
 | 
					 | 
				
			||||||
            <g transform="rotate(-45)">
 | 
					 | 
				
			||||||
               <line transform="translate(0,9)" y2="3" fill="none" stroke="#ffa500" stroke-linecap="round"
 | 
					 | 
				
			||||||
                  stroke-width="2" />
 | 
					 | 
				
			||||||
            </g>
 | 
					 | 
				
			||||||
         </g>
 | 
					 | 
				
			||||||
         <circle r="5" fill="#ffa500" stroke="#ffa500" stroke-width="2" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
      <g class="am-weather-cloud-2"
 | 
					 | 
				
			||||||
         style="-moz-animation-duration:3s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-cloud-2;-moz-animation-timing-function:linear;-webkit-animation-duration:3s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-cloud-2;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
         <path transform="translate(-20,-11)"
 | 
					 | 
				
			||||||
            d="m47.7 35.4c0-4.6-3.7-8.2-8.2-8.2-1 0-1.9 0.2-2.8 0.5-0.3-3.4-3.1-6.2-6.6-6.2-3.7 0-6.7 3-6.7 6.7 0 0.8 0.2 1.6 0.4 2.3-0.3-0.1-0.7-0.1-1-0.1-3.7 0-6.7 3-6.7 6.7 0 3.6 2.9 6.6 6.5 6.7h17.2c4.4-0.5 7.9-4 7.9-8.4z"
 | 
					 | 
				
			||||||
            fill="#57a0ee" stroke="#fff" stroke-linejoin="round" stroke-width="1.2" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
      <g class="am-weather-snow-1"
 | 
					 | 
				
			||||||
         style="-moz-animation-duration:2s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-snow;-moz-animation-timing-function:linear;-ms-animation-duration:2s;-ms-animation-iteration-count:infinite;-ms-animation-name:am-weather-snow;-ms-animation-timing-function:linear;-webkit-animation-duration:2s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-snow;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
         <g transform="translate(3,28)" fill="none" stroke="#57a0ee" stroke-linecap="round">
 | 
					 | 
				
			||||||
            <line transform="translate(0,9)" y1="-2.5" y2="2.5" stroke-width="1.2" />
 | 
					 | 
				
			||||||
            <line transform="rotate(45,-10.864,4.5)" y1="-2.5" y2="2.5" />
 | 
					 | 
				
			||||||
            <line transform="rotate(90,-4.5,4.5)" y1="-2.5" y2="2.5" />
 | 
					 | 
				
			||||||
            <line transform="rotate(135,-1.864,4.5)" y1="-2.5" y2="2.5" />
 | 
					 | 
				
			||||||
         </g>
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
      <g class="am-weather-snow-2"
 | 
					 | 
				
			||||||
         style="-moz-animation-delay:1.2s;-moz-animation-duration:2s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-snow;-moz-animation-timing-function:linear;-ms-animation-delay:1.2s;-ms-animation-duration:2s;-ms-animation-iteration-count:infinite;-ms-animation-name:am-weather-snow;-ms-animation-timing-function:linear;-webkit-animation-delay:1.2s;-webkit-animation-duration:2s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-snow;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
         <g transform="translate(11,28)" fill="none" stroke="#57a0ee" stroke-linecap="round">
 | 
					 | 
				
			||||||
            <line transform="translate(0,9)" y1="-2.5" y2="2.5" stroke-width="1.2" />
 | 
					 | 
				
			||||||
            <line transform="rotate(45,-10.864,4.5)" y1="-2.5" y2="2.5" />
 | 
					 | 
				
			||||||
            <line transform="rotate(90,-4.5,4.5)" y1="-2.5" y2="2.5" />
 | 
					 | 
				
			||||||
            <line transform="rotate(135,-1.864,4.5)" y1="-2.5" y2="2.5" />
 | 
					 | 
				
			||||||
         </g>
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
      <g class="am-weather-snow-3"
 | 
					 | 
				
			||||||
         style="-moz-animation-duration:2s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-snow-reverse;-moz-animation-timing-function:linear;-ms-animation-duration:2s;-ms-animation-iteration-count:infinite;-ms-animation-name:am-weather-snow-reverse;-ms-animation-timing-function:linear;-webkit-animation-duration:2s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-snow-reverse;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
         <g transform="translate(20,28)" fill="none" stroke="#57a0ee" stroke-linecap="round">
 | 
					 | 
				
			||||||
            <line transform="translate(0,9)" y1="-2.5" y2="2.5" stroke-width="1.2" />
 | 
					 | 
				
			||||||
            <line transform="rotate(45,-10.864,4.5)" y1="-2.5" y2="2.5" />
 | 
					 | 
				
			||||||
            <line transform="rotate(90,-4.5,4.5)" y1="-2.5" y2="2.5" />
 | 
					 | 
				
			||||||
            <line transform="rotate(135,-1.864,4.5)" y1="-2.5" y2="2.5" />
 | 
					 | 
				
			||||||
         </g>
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
   </g>
 | 
					 | 
				
			||||||
</svg>
 | 
					 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 15 KiB  | 
@@ -1,361 +0,0 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
					 | 
				
			||||||
<!-- (c) ammap.com | SVG weather icons -->
 | 
					 | 
				
			||||||
<svg width="56" height="48" version="1.1" xmlns="http://www.w3.org/2000/svg">
 | 
					 | 
				
			||||||
   <defs>
 | 
					 | 
				
			||||||
      <filter id="blur" x="-.24684" y="-.26897" width="1.4937" height="1.6759">
 | 
					 | 
				
			||||||
         <feGaussianBlur in="SourceAlpha" stdDeviation="3" />
 | 
					 | 
				
			||||||
         <feOffset dx="0" dy="4" result="offsetblur" />
 | 
					 | 
				
			||||||
         <feComponentTransfer>
 | 
					 | 
				
			||||||
            <feFuncA slope="0.05" type="linear" />
 | 
					 | 
				
			||||||
         </feComponentTransfer>
 | 
					 | 
				
			||||||
         <feMerge>
 | 
					 | 
				
			||||||
            <feMergeNode />
 | 
					 | 
				
			||||||
            <feMergeNode in="SourceGraphic" />
 | 
					 | 
				
			||||||
         </feMerge>
 | 
					 | 
				
			||||||
      </filter>
 | 
					 | 
				
			||||||
      <style type="text/css">
 | 
					 | 
				
			||||||
         <![CDATA[
 | 
					 | 
				
			||||||
         /*
 | 
					 | 
				
			||||||
** CLOUDS
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
         @keyframes am-weather-cloud-2 {
 | 
					 | 
				
			||||||
            0% {
 | 
					 | 
				
			||||||
               -webkit-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
               -moz-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
               -ms-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
               transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            50% {
 | 
					 | 
				
			||||||
               -webkit-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
               -moz-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
               -ms-transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
               transform: translate(2px, 0px);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            100% {
 | 
					 | 
				
			||||||
               -webkit-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
               -moz-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
               -ms-transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
               transform: translate(0px, 0px);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
         }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
         .am-weather-cloud-2 {
 | 
					 | 
				
			||||||
            -webkit-animation-name: am-weather-cloud-2;
 | 
					 | 
				
			||||||
            -moz-animation-name: am-weather-cloud-2;
 | 
					 | 
				
			||||||
            animation-name: am-weather-cloud-2;
 | 
					 | 
				
			||||||
            -webkit-animation-duration: 3s;
 | 
					 | 
				
			||||||
            -moz-animation-duration: 3s;
 | 
					 | 
				
			||||||
            animation-duration: 3s;
 | 
					 | 
				
			||||||
            -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
            -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
            animation-timing-function: linear;
 | 
					 | 
				
			||||||
            -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
            -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
            animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
         }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
         /*
 | 
					 | 
				
			||||||
** MOON
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
         @keyframes am-weather-moon {
 | 
					 | 
				
			||||||
            0% {
 | 
					 | 
				
			||||||
               -webkit-transform: rotate(0deg);
 | 
					 | 
				
			||||||
               -moz-transform: rotate(0deg);
 | 
					 | 
				
			||||||
               -ms-transform: rotate(0deg);
 | 
					 | 
				
			||||||
               transform: rotate(0deg);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            50% {
 | 
					 | 
				
			||||||
               -webkit-transform: rotate(15deg);
 | 
					 | 
				
			||||||
               -moz-transform: rotate(15deg);
 | 
					 | 
				
			||||||
               -ms-transform: rotate(15deg);
 | 
					 | 
				
			||||||
               transform: rotate(15deg);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            100% {
 | 
					 | 
				
			||||||
               -webkit-transform: rotate(0deg);
 | 
					 | 
				
			||||||
               -moz-transform: rotate(0deg);
 | 
					 | 
				
			||||||
               -ms-transform: rotate(0deg);
 | 
					 | 
				
			||||||
               transform: rotate(0deg);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
         }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
         .am-weather-moon {
 | 
					 | 
				
			||||||
            -webkit-animation-name: am-weather-moon;
 | 
					 | 
				
			||||||
            -moz-animation-name: am-weather-moon;
 | 
					 | 
				
			||||||
            -ms-animation-name: am-weather-moon;
 | 
					 | 
				
			||||||
            animation-name: am-weather-moon;
 | 
					 | 
				
			||||||
            -webkit-animation-duration: 6s;
 | 
					 | 
				
			||||||
            -moz-animation-duration: 6s;
 | 
					 | 
				
			||||||
            -ms-animation-duration: 6s;
 | 
					 | 
				
			||||||
            animation-duration: 6s;
 | 
					 | 
				
			||||||
            -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
            -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
            -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
            animation-timing-function: linear;
 | 
					 | 
				
			||||||
            -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
            -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
            -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
            animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
            -webkit-transform-origin: 12.5px 15.15px 0;
 | 
					 | 
				
			||||||
            /* TODO FF CENTER ISSUE */
 | 
					 | 
				
			||||||
            -moz-transform-origin: 12.5px 15.15px 0;
 | 
					 | 
				
			||||||
            /* TODO FF CENTER ISSUE */
 | 
					 | 
				
			||||||
            -ms-transform-origin: 12.5px 15.15px 0;
 | 
					 | 
				
			||||||
            /* TODO FF CENTER ISSUE */
 | 
					 | 
				
			||||||
            transform-origin: 12.5px 15.15px 0;
 | 
					 | 
				
			||||||
            /* TODO FF CENTER ISSUE */
 | 
					 | 
				
			||||||
         }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
         @keyframes am-weather-moon-star-1 {
 | 
					 | 
				
			||||||
            0% {
 | 
					 | 
				
			||||||
               opacity: 0;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            100% {
 | 
					 | 
				
			||||||
               opacity: 1;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
         }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
         .am-weather-moon-star-1 {
 | 
					 | 
				
			||||||
            -webkit-animation-name: am-weather-moon-star-1;
 | 
					 | 
				
			||||||
            -moz-animation-name: am-weather-moon-star-1;
 | 
					 | 
				
			||||||
            -ms-animation-name: am-weather-moon-star-1;
 | 
					 | 
				
			||||||
            animation-name: am-weather-moon-star-1;
 | 
					 | 
				
			||||||
            -webkit-animation-delay: 3s;
 | 
					 | 
				
			||||||
            -moz-animation-delay: 3s;
 | 
					 | 
				
			||||||
            -ms-animation-delay: 3s;
 | 
					 | 
				
			||||||
            animation-delay: 3s;
 | 
					 | 
				
			||||||
            -webkit-animation-duration: 5s;
 | 
					 | 
				
			||||||
            -moz-animation-duration: 5s;
 | 
					 | 
				
			||||||
            -ms-animation-duration: 5s;
 | 
					 | 
				
			||||||
            animation-duration: 5s;
 | 
					 | 
				
			||||||
            -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
            -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
            -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
            animation-timing-function: linear;
 | 
					 | 
				
			||||||
            -webkit-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
            -moz-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
            -ms-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
            animation-iteration-count: 1;
 | 
					 | 
				
			||||||
         }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
         @keyframes am-weather-moon-star-2 {
 | 
					 | 
				
			||||||
            0% {
 | 
					 | 
				
			||||||
               opacity: 0;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            100% {
 | 
					 | 
				
			||||||
               opacity: 1;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
         }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
         .am-weather-moon-star-2 {
 | 
					 | 
				
			||||||
            -webkit-animation-name: am-weather-moon-star-2;
 | 
					 | 
				
			||||||
            -moz-animation-name: am-weather-moon-star-2;
 | 
					 | 
				
			||||||
            -ms-animation-name: am-weather-moon-star-2;
 | 
					 | 
				
			||||||
            animation-name: am-weather-moon-star-2;
 | 
					 | 
				
			||||||
            -webkit-animation-delay: 5s;
 | 
					 | 
				
			||||||
            -moz-animation-delay: 5s;
 | 
					 | 
				
			||||||
            -ms-animation-delay: 5s;
 | 
					 | 
				
			||||||
            animation-delay: 5s;
 | 
					 | 
				
			||||||
            -webkit-animation-duration: 4s;
 | 
					 | 
				
			||||||
            -moz-animation-duration: 4s;
 | 
					 | 
				
			||||||
            -ms-animation-duration: 4s;
 | 
					 | 
				
			||||||
            animation-duration: 4s;
 | 
					 | 
				
			||||||
            -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
            -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
            -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
            animation-timing-function: linear;
 | 
					 | 
				
			||||||
            -webkit-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
            -moz-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
            -ms-animation-iteration-count: 1;
 | 
					 | 
				
			||||||
            animation-iteration-count: 1;
 | 
					 | 
				
			||||||
         }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
         /*
 | 
					 | 
				
			||||||
** SNOW
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
         @keyframes am-weather-snow {
 | 
					 | 
				
			||||||
            0% {
 | 
					 | 
				
			||||||
               -webkit-transform: translateX(0) translateY(0);
 | 
					 | 
				
			||||||
               -moz-transform: translateX(0) translateY(0);
 | 
					 | 
				
			||||||
               -ms-transform: translateX(0) translateY(0);
 | 
					 | 
				
			||||||
               transform: translateX(0) translateY(0);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            33.33% {
 | 
					 | 
				
			||||||
               -webkit-transform: translateX(-1.2px) translateY(2px);
 | 
					 | 
				
			||||||
               -moz-transform: translateX(-1.2px) translateY(2px);
 | 
					 | 
				
			||||||
               -ms-transform: translateX(-1.2px) translateY(2px);
 | 
					 | 
				
			||||||
               transform: translateX(-1.2px) translateY(2px);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            66.66% {
 | 
					 | 
				
			||||||
               -webkit-transform: translateX(1.4px) translateY(4px);
 | 
					 | 
				
			||||||
               -moz-transform: translateX(1.4px) translateY(4px);
 | 
					 | 
				
			||||||
               -ms-transform: translateX(1.4px) translateY(4px);
 | 
					 | 
				
			||||||
               transform: translateX(1.4px) translateY(4px);
 | 
					 | 
				
			||||||
               opacity: 1;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            100% {
 | 
					 | 
				
			||||||
               -webkit-transform: translateX(-1.6px) translateY(6px);
 | 
					 | 
				
			||||||
               -moz-transform: translateX(-1.6px) translateY(6px);
 | 
					 | 
				
			||||||
               -ms-transform: translateX(-1.6px) translateY(6px);
 | 
					 | 
				
			||||||
               transform: translateX(-1.6px) translateY(6px);
 | 
					 | 
				
			||||||
               opacity: 0;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
         }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
         @keyframes am-weather-snow-reverse {
 | 
					 | 
				
			||||||
            0% {
 | 
					 | 
				
			||||||
               -webkit-transform: translateX(0) translateY(0);
 | 
					 | 
				
			||||||
               -moz-transform: translateX(0) translateY(0);
 | 
					 | 
				
			||||||
               -ms-transform: translateX(0) translateY(0);
 | 
					 | 
				
			||||||
               transform: translateX(0) translateY(0);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            33.33% {
 | 
					 | 
				
			||||||
               -webkit-transform: translateX(1.2px) translateY(2px);
 | 
					 | 
				
			||||||
               -moz-transform: translateX(1.2px) translateY(2px);
 | 
					 | 
				
			||||||
               -ms-transform: translateX(1.2px) translateY(2px);
 | 
					 | 
				
			||||||
               transform: translateX(1.2px) translateY(2px);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            66.66% {
 | 
					 | 
				
			||||||
               -webkit-transform: translateX(-1.4px) translateY(4px);
 | 
					 | 
				
			||||||
               -moz-transform: translateX(-1.4px) translateY(4px);
 | 
					 | 
				
			||||||
               -ms-transform: translateX(-1.4px) translateY(4px);
 | 
					 | 
				
			||||||
               transform: translateX(-1.4px) translateY(4px);
 | 
					 | 
				
			||||||
               opacity: 1;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            100% {
 | 
					 | 
				
			||||||
               -webkit-transform: translateX(1.6px) translateY(6px);
 | 
					 | 
				
			||||||
               -moz-transform: translateX(1.6px) translateY(6px);
 | 
					 | 
				
			||||||
               -ms-transform: translateX(1.6px) translateY(6px);
 | 
					 | 
				
			||||||
               transform: translateX(1.6px) translateY(6px);
 | 
					 | 
				
			||||||
               opacity: 0;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
         }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
         .am-weather-snow-1 {
 | 
					 | 
				
			||||||
            -webkit-animation-name: am-weather-snow;
 | 
					 | 
				
			||||||
            -moz-animation-name: am-weather-snow;
 | 
					 | 
				
			||||||
            -ms-animation-name: am-weather-snow;
 | 
					 | 
				
			||||||
            animation-name: am-weather-snow;
 | 
					 | 
				
			||||||
            -webkit-animation-duration: 2s;
 | 
					 | 
				
			||||||
            -moz-animation-duration: 2s;
 | 
					 | 
				
			||||||
            -ms-animation-duration: 2s;
 | 
					 | 
				
			||||||
            animation-duration: 2s;
 | 
					 | 
				
			||||||
            -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
            -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
            -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
            animation-timing-function: linear;
 | 
					 | 
				
			||||||
            -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
            -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
            -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
            animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
         }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
         .am-weather-snow-2 {
 | 
					 | 
				
			||||||
            -webkit-animation-name: am-weather-snow;
 | 
					 | 
				
			||||||
            -moz-animation-name: am-weather-snow;
 | 
					 | 
				
			||||||
            -ms-animation-name: am-weather-snow;
 | 
					 | 
				
			||||||
            animation-name: am-weather-snow;
 | 
					 | 
				
			||||||
            -webkit-animation-delay: 1.2s;
 | 
					 | 
				
			||||||
            -moz-animation-delay: 1.2s;
 | 
					 | 
				
			||||||
            -ms-animation-delay: 1.2s;
 | 
					 | 
				
			||||||
            animation-delay: 1.2s;
 | 
					 | 
				
			||||||
            -webkit-animation-duration: 2s;
 | 
					 | 
				
			||||||
            -moz-animation-duration: 2s;
 | 
					 | 
				
			||||||
            -ms-animation-duration: 2s;
 | 
					 | 
				
			||||||
            animation-duration: 2s;
 | 
					 | 
				
			||||||
            -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
            -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
            -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
            animation-timing-function: linear;
 | 
					 | 
				
			||||||
            -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
            -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
            -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
            animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
         }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
         .am-weather-snow-3 {
 | 
					 | 
				
			||||||
            -webkit-animation-name: am-weather-snow-reverse;
 | 
					 | 
				
			||||||
            -moz-animation-name: am-weather-snow-reverse;
 | 
					 | 
				
			||||||
            -ms-animation-name: am-weather-snow-reverse;
 | 
					 | 
				
			||||||
            animation-name: am-weather-snow-reverse;
 | 
					 | 
				
			||||||
            -webkit-animation-duration: 2s;
 | 
					 | 
				
			||||||
            -moz-animation-duration: 2s;
 | 
					 | 
				
			||||||
            -ms-animation-duration: 2s;
 | 
					 | 
				
			||||||
            animation-duration: 2s;
 | 
					 | 
				
			||||||
            -webkit-animation-timing-function: linear;
 | 
					 | 
				
			||||||
            -moz-animation-timing-function: linear;
 | 
					 | 
				
			||||||
            -ms-animation-timing-function: linear;
 | 
					 | 
				
			||||||
            animation-timing-function: linear;
 | 
					 | 
				
			||||||
            -webkit-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
            -moz-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
            -ms-animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
            animation-iteration-count: infinite;
 | 
					 | 
				
			||||||
         }
 | 
					 | 
				
			||||||
         ]]>
 | 
					 | 
				
			||||||
      </style>
 | 
					 | 
				
			||||||
   </defs>
 | 
					 | 
				
			||||||
   <g transform="translate(16,-2)" filter="url(#blur)">
 | 
					 | 
				
			||||||
      <g transform="matrix(.8 0 0 .8 16 4)">
 | 
					 | 
				
			||||||
         <g class="am-weather-moon-star-1"
 | 
					 | 
				
			||||||
            style="-moz-animation-delay:3s;-moz-animation-duration:5s;-moz-animation-iteration-count:1;-moz-animation-name:am-weather-moon-star-1;-moz-animation-timing-function:linear;-ms-animation-delay:3s;-ms-animation-duration:5s;-ms-animation-iteration-count:1;-ms-animation-name:am-weather-moon-star-1;-ms-animation-timing-function:linear;-webkit-animation-delay:3s;-webkit-animation-duration:5s;-webkit-animation-iteration-count:1;-webkit-animation-name:am-weather-moon-star-1;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
            <polygon points="4 4 3.3 5.2 2.7 4 1.5 3.3 2.7 2.7 3.3 1.5 4 2.7 5.2 3.3" fill="#ffa500"
 | 
					 | 
				
			||||||
               stroke-miterlimit="10" />
 | 
					 | 
				
			||||||
         </g>
 | 
					 | 
				
			||||||
         <g class="am-weather-moon-star-2"
 | 
					 | 
				
			||||||
            style="-moz-animation-delay:5s;-moz-animation-duration:4s;-moz-animation-iteration-count:1;-moz-animation-name:am-weather-moon-star-2;-moz-animation-timing-function:linear;-ms-animation-delay:5s;-ms-animation-duration:4s;-ms-animation-iteration-count:1;-ms-animation-name:am-weather-moon-star-2;-ms-animation-timing-function:linear;-webkit-animation-delay:5s;-webkit-animation-duration:4s;-webkit-animation-iteration-count:1;-webkit-animation-name:am-weather-moon-star-2;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
            <polygon transform="translate(20,10)" points="4 4 3.3 5.2 2.7 4 1.5 3.3 2.7 2.7 3.3 1.5 4 2.7 5.2 3.3"
 | 
					 | 
				
			||||||
               fill="#ffa500" stroke-miterlimit="10" />
 | 
					 | 
				
			||||||
         </g>
 | 
					 | 
				
			||||||
         <g class="am-weather-moon"
 | 
					 | 
				
			||||||
            style="-moz-animation-duration:6s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-moon;-moz-animation-timing-function:linear;-moz-transform-origin:12.5px 15.15px 0;-ms-animation-duration:6s;-ms-animation-iteration-count:infinite;-ms-animation-name:am-weather-moon;-ms-animation-timing-function:linear;-ms-transform-origin:12.5px 15.15px 0;-webkit-animation-duration:6s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-moon;-webkit-animation-timing-function:linear;-webkit-transform-origin:12.5px 15.15px 0">
 | 
					 | 
				
			||||||
            <path
 | 
					 | 
				
			||||||
               d="m14.5 13.2c0-3.7 2-6.9 5-8.7-1.5-0.9-3.2-1.3-5-1.3-5.5 0-10 4.5-10 10s4.5 10 10 10c1.8 0 3.5-0.5 5-1.3-3-1.7-5-5-5-8.7z"
 | 
					 | 
				
			||||||
               fill="#ffa500" stroke="#ffa500" stroke-linejoin="round" stroke-width="2" />
 | 
					 | 
				
			||||||
         </g>
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
      <g class="am-weather-cloud-2"
 | 
					 | 
				
			||||||
         style="-moz-animation-duration:3s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-cloud-2;-moz-animation-timing-function:linear;-webkit-animation-duration:3s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-cloud-2;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
         <path transform="translate(-20,-11)"
 | 
					 | 
				
			||||||
            d="m47.7 35.4c0-4.6-3.7-8.2-8.2-8.2-1 0-1.9 0.2-2.8 0.5-0.3-3.4-3.1-6.2-6.6-6.2-3.7 0-6.7 3-6.7 6.7 0 0.8 0.2 1.6 0.4 2.3-0.3-0.1-0.7-0.1-1-0.1-3.7 0-6.7 3-6.7 6.7 0 3.6 2.9 6.6 6.5 6.7h17.2c4.4-0.5 7.9-4 7.9-8.4z"
 | 
					 | 
				
			||||||
            fill="#57a0ee" stroke="#fff" stroke-linejoin="round" stroke-width="1.2" />
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
      <g class="am-weather-snow-1"
 | 
					 | 
				
			||||||
         style="-moz-animation-duration:2s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-snow;-moz-animation-timing-function:linear;-ms-animation-duration:2s;-ms-animation-iteration-count:infinite;-ms-animation-name:am-weather-snow;-ms-animation-timing-function:linear;-webkit-animation-duration:2s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-snow;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
         <g transform="translate(3,28)" fill="none" stroke="#57a0ee" stroke-linecap="round">
 | 
					 | 
				
			||||||
            <line transform="translate(0,9)" y1="-2.5" y2="2.5" stroke-width="1.2" />
 | 
					 | 
				
			||||||
            <line transform="rotate(45,-10.864,4.5)" y1="-2.5" y2="2.5" />
 | 
					 | 
				
			||||||
            <line transform="rotate(90,-4.5,4.5)" y1="-2.5" y2="2.5" />
 | 
					 | 
				
			||||||
            <line transform="rotate(135,-1.864,4.5)" y1="-2.5" y2="2.5" />
 | 
					 | 
				
			||||||
         </g>
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
      <g class="am-weather-snow-2"
 | 
					 | 
				
			||||||
         style="-moz-animation-delay:1.2s;-moz-animation-duration:2s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-snow;-moz-animation-timing-function:linear;-ms-animation-delay:1.2s;-ms-animation-duration:2s;-ms-animation-iteration-count:infinite;-ms-animation-name:am-weather-snow;-ms-animation-timing-function:linear;-webkit-animation-delay:1.2s;-webkit-animation-duration:2s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-snow;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
         <g transform="translate(11,28)" fill="none" stroke="#57a0ee" stroke-linecap="round">
 | 
					 | 
				
			||||||
            <line transform="translate(0,9)" y1="-2.5" y2="2.5" stroke-width="1.2" />
 | 
					 | 
				
			||||||
            <line transform="rotate(45,-10.864,4.5)" y1="-2.5" y2="2.5" />
 | 
					 | 
				
			||||||
            <line transform="rotate(90,-4.5,4.5)" y1="-2.5" y2="2.5" />
 | 
					 | 
				
			||||||
            <line transform="rotate(135,-1.864,4.5)" y1="-2.5" y2="2.5" />
 | 
					 | 
				
			||||||
         </g>
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
      <g class="am-weather-snow-3"
 | 
					 | 
				
			||||||
         style="-moz-animation-duration:2s;-moz-animation-iteration-count:infinite;-moz-animation-name:am-weather-snow-reverse;-moz-animation-timing-function:linear;-ms-animation-duration:2s;-ms-animation-iteration-count:infinite;-ms-animation-name:am-weather-snow-reverse;-ms-animation-timing-function:linear;-webkit-animation-duration:2s;-webkit-animation-iteration-count:infinite;-webkit-animation-name:am-weather-snow-reverse;-webkit-animation-timing-function:linear">
 | 
					 | 
				
			||||||
         <g transform="translate(20,28)" fill="none" stroke="#57a0ee" stroke-linecap="round">
 | 
					 | 
				
			||||||
            <line transform="translate(0,9)" y1="-2.5" y2="2.5" stroke-width="1.2" />
 | 
					 | 
				
			||||||
            <line transform="rotate(45,-10.864,4.5)" y1="-2.5" y2="2.5" />
 | 
					 | 
				
			||||||
            <line transform="rotate(90,-4.5,4.5)" y1="-2.5" y2="2.5" />
 | 
					 | 
				
			||||||
            <line transform="rotate(135,-1.864,4.5)" y1="-2.5" y2="2.5" />
 | 
					 | 
				
			||||||
         </g>
 | 
					 | 
				
			||||||
      </g>
 | 
					 | 
				
			||||||
   </g>
 | 
					 | 
				
			||||||
</svg>
 | 
					 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 17 KiB  | 
							
								
								
									
										29
									
								
								sample.config.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,29 @@
 | 
				
			|||||||
 | 
					[GENERAL]
 | 
				
			||||||
 | 
					SIMILARITY_MEASURE = "cosine" # "cosine" or "dot"
 | 
				
			||||||
 | 
					KEEP_ALIVE = "5m" # How long to keep Ollama models loaded into memory. (Instead of using -1 use "-1m")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[MODELS.OPENAI]
 | 
				
			||||||
 | 
					API_KEY = ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[MODELS.GROQ]
 | 
				
			||||||
 | 
					API_KEY = ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[MODELS.ANTHROPIC]
 | 
				
			||||||
 | 
					API_KEY = ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[MODELS.GEMINI]
 | 
				
			||||||
 | 
					API_KEY = ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[MODELS.CUSTOM_OPENAI]
 | 
				
			||||||
 | 
					API_KEY = ""
 | 
				
			||||||
 | 
					API_URL = ""
 | 
				
			||||||
 | 
					MODEL_NAME = ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[MODELS.OLLAMA]
 | 
				
			||||||
 | 
					API_URL = "" # Ollama API URL - http://host.docker.internal:11434
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[MODELS.DEEPSEEK]
 | 
				
			||||||
 | 
					API_KEY = ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[API_ENDPOINTS]
 | 
				
			||||||
 | 
					SEARXNG = "" # SearxNG API URL - http://localhost:32768
 | 
				
			||||||
@@ -1,104 +1,66 @@
 | 
				
			|||||||
 | 
					import prompts from '@/lib/prompts';
 | 
				
			||||||
 | 
					import MetaSearchAgent from '@/lib/search/metaSearchAgent';
 | 
				
			||||||
import crypto from 'crypto';
 | 
					import crypto from 'crypto';
 | 
				
			||||||
import { AIMessage, BaseMessage, HumanMessage } from '@langchain/core/messages';
 | 
					import { AIMessage, BaseMessage, HumanMessage } from '@langchain/core/messages';
 | 
				
			||||||
import { EventEmitter } from 'stream';
 | 
					import { EventEmitter } from 'stream';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  chatModelProviders,
 | 
				
			||||||
 | 
					  embeddingModelProviders,
 | 
				
			||||||
 | 
					  getAvailableChatModelProviders,
 | 
				
			||||||
 | 
					  getAvailableEmbeddingModelProviders,
 | 
				
			||||||
 | 
					} from '@/lib/providers';
 | 
				
			||||||
import db from '@/lib/db';
 | 
					import db from '@/lib/db';
 | 
				
			||||||
import { chats, messages as messagesSchema } from '@/lib/db/schema';
 | 
					import { chats, messages as messagesSchema } from '@/lib/db/schema';
 | 
				
			||||||
import { and, eq, gt } from 'drizzle-orm';
 | 
					import { and, eq, gt } from 'drizzle-orm';
 | 
				
			||||||
import { getFileDetails } from '@/lib/utils/files';
 | 
					import { getFileDetails } from '@/lib/utils/files';
 | 
				
			||||||
 | 
					import { BaseChatModel } from '@langchain/core/language_models/chat_models';
 | 
				
			||||||
 | 
					import { ChatOpenAI } from '@langchain/openai';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  getCustomOpenaiApiKey,
 | 
				
			||||||
 | 
					  getCustomOpenaiApiUrl,
 | 
				
			||||||
 | 
					  getCustomOpenaiModelName,
 | 
				
			||||||
 | 
					} from '@/lib/config';
 | 
				
			||||||
import { searchHandlers } from '@/lib/search';
 | 
					import { searchHandlers } from '@/lib/search';
 | 
				
			||||||
import { z } from 'zod';
 | 
					 | 
				
			||||||
import ModelRegistry from '@/lib/models/registry';
 | 
					 | 
				
			||||||
import { ModelWithProvider } from '@/lib/models/types';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const runtime = 'nodejs';
 | 
					export const runtime = 'nodejs';
 | 
				
			||||||
export const dynamic = 'force-dynamic';
 | 
					export const dynamic = 'force-dynamic';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const messageSchema = z.object({
 | 
					type Message = {
 | 
				
			||||||
  messageId: z.string().min(1, 'Message ID is required'),
 | 
					  messageId: string;
 | 
				
			||||||
  chatId: z.string().min(1, 'Chat ID is required'),
 | 
					  chatId: string;
 | 
				
			||||||
  content: z.string().min(1, 'Message content is required'),
 | 
					  content: string;
 | 
				
			||||||
});
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const chatModelSchema: z.ZodType<ModelWithProvider> = z.object({
 | 
					type ChatModel = {
 | 
				
			||||||
  providerId: z.string({
 | 
					  provider: string;
 | 
				
			||||||
    errorMap: () => ({
 | 
					  name: string;
 | 
				
			||||||
      message: 'Chat model provider id must be provided',
 | 
					};
 | 
				
			||||||
    }),
 | 
					 | 
				
			||||||
  }),
 | 
					 | 
				
			||||||
  key: z.string({
 | 
					 | 
				
			||||||
    errorMap: () => ({
 | 
					 | 
				
			||||||
      message: 'Chat model key must be provided',
 | 
					 | 
				
			||||||
    }),
 | 
					 | 
				
			||||||
  }),
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const embeddingModelSchema: z.ZodType<ModelWithProvider> = z.object({
 | 
					type EmbeddingModel = {
 | 
				
			||||||
  providerId: z.string({
 | 
					  provider: string;
 | 
				
			||||||
    errorMap: () => ({
 | 
					  name: string;
 | 
				
			||||||
      message: 'Embedding model provider id must be provided',
 | 
					};
 | 
				
			||||||
    }),
 | 
					 | 
				
			||||||
  }),
 | 
					 | 
				
			||||||
  key: z.string({
 | 
					 | 
				
			||||||
    errorMap: () => ({
 | 
					 | 
				
			||||||
      message: 'Embedding model key must be provided',
 | 
					 | 
				
			||||||
    }),
 | 
					 | 
				
			||||||
  }),
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const bodySchema = z.object({
 | 
					type Body = {
 | 
				
			||||||
  message: messageSchema,
 | 
					  message: Message;
 | 
				
			||||||
  optimizationMode: z.enum(['speed', 'balanced', 'quality'], {
 | 
					  optimizationMode: 'speed' | 'balanced' | 'quality';
 | 
				
			||||||
    errorMap: () => ({
 | 
					  focusMode: string;
 | 
				
			||||||
      message: 'Optimization mode must be one of: speed, balanced, quality',
 | 
					  history: Array<[string, string]>;
 | 
				
			||||||
    }),
 | 
					  files: Array<string>;
 | 
				
			||||||
  }),
 | 
					  chatModel: ChatModel;
 | 
				
			||||||
  focusMode: z.string().min(1, 'Focus mode is required'),
 | 
					  embeddingModel: EmbeddingModel;
 | 
				
			||||||
  history: z
 | 
					  systemInstructions: string;
 | 
				
			||||||
    .array(
 | 
					 | 
				
			||||||
      z.tuple([z.string(), z.string()], {
 | 
					 | 
				
			||||||
        errorMap: () => ({
 | 
					 | 
				
			||||||
          message: 'History items must be tuples of two strings',
 | 
					 | 
				
			||||||
        }),
 | 
					 | 
				
			||||||
      }),
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    .optional()
 | 
					 | 
				
			||||||
    .default([]),
 | 
					 | 
				
			||||||
  files: z.array(z.string()).optional().default([]),
 | 
					 | 
				
			||||||
  chatModel: chatModelSchema,
 | 
					 | 
				
			||||||
  embeddingModel: embeddingModelSchema,
 | 
					 | 
				
			||||||
  systemInstructions: z.string().nullable().optional().default(''),
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type Message = z.infer<typeof messageSchema>;
 | 
					 | 
				
			||||||
type Body = z.infer<typeof bodySchema>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const safeValidateBody = (data: unknown) => {
 | 
					 | 
				
			||||||
  const result = bodySchema.safeParse(data);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (!result.success) {
 | 
					 | 
				
			||||||
    return {
 | 
					 | 
				
			||||||
      success: false,
 | 
					 | 
				
			||||||
      error: result.error.errors.map((e) => ({
 | 
					 | 
				
			||||||
        path: e.path.join('.'),
 | 
					 | 
				
			||||||
        message: e.message,
 | 
					 | 
				
			||||||
      })),
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return {
 | 
					 | 
				
			||||||
    success: true,
 | 
					 | 
				
			||||||
    data: result.data,
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const handleEmitterEvents = async (
 | 
					const handleEmitterEvents = async (
 | 
				
			||||||
  stream: EventEmitter,
 | 
					  stream: EventEmitter,
 | 
				
			||||||
  writer: WritableStreamDefaultWriter,
 | 
					  writer: WritableStreamDefaultWriter,
 | 
				
			||||||
  encoder: TextEncoder,
 | 
					  encoder: TextEncoder,
 | 
				
			||||||
 | 
					  aiMessageId: string,
 | 
				
			||||||
  chatId: string,
 | 
					  chatId: string,
 | 
				
			||||||
) => {
 | 
					) => {
 | 
				
			||||||
  let receivedMessage = '';
 | 
					  let recievedMessage = '';
 | 
				
			||||||
  const aiMessageId = crypto.randomBytes(7).toString('hex');
 | 
					  let sources: any[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  stream.on('data', (data) => {
 | 
					  stream.on('data', (data) => {
 | 
				
			||||||
    const parsedData = JSON.parse(data);
 | 
					    const parsedData = JSON.parse(data);
 | 
				
			||||||
@@ -113,7 +75,7 @@ const handleEmitterEvents = async (
 | 
				
			|||||||
        ),
 | 
					        ),
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      receivedMessage += parsedData.data;
 | 
					      recievedMessage += parsedData.data;
 | 
				
			||||||
    } else if (parsedData.type === 'sources') {
 | 
					    } else if (parsedData.type === 'sources') {
 | 
				
			||||||
      writer.write(
 | 
					      writer.write(
 | 
				
			||||||
        encoder.encode(
 | 
					        encoder.encode(
 | 
				
			||||||
@@ -125,17 +87,7 @@ const handleEmitterEvents = async (
 | 
				
			|||||||
        ),
 | 
					        ),
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const sourceMessageId = crypto.randomBytes(7).toString('hex');
 | 
					      sources = parsedData.data;
 | 
				
			||||||
 | 
					 | 
				
			||||||
      db.insert(messagesSchema)
 | 
					 | 
				
			||||||
        .values({
 | 
					 | 
				
			||||||
          chatId: chatId,
 | 
					 | 
				
			||||||
          messageId: sourceMessageId,
 | 
					 | 
				
			||||||
          role: 'source',
 | 
					 | 
				
			||||||
          sources: parsedData.data,
 | 
					 | 
				
			||||||
          createdAt: new Date().toString(),
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
        .execute();
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
  stream.on('end', () => {
 | 
					  stream.on('end', () => {
 | 
				
			||||||
@@ -143,6 +95,7 @@ const handleEmitterEvents = async (
 | 
				
			|||||||
      encoder.encode(
 | 
					      encoder.encode(
 | 
				
			||||||
        JSON.stringify({
 | 
					        JSON.stringify({
 | 
				
			||||||
          type: 'messageEnd',
 | 
					          type: 'messageEnd',
 | 
				
			||||||
 | 
					          messageId: aiMessageId,
 | 
				
			||||||
        }) + '\n',
 | 
					        }) + '\n',
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
@@ -150,11 +103,14 @@ const handleEmitterEvents = async (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    db.insert(messagesSchema)
 | 
					    db.insert(messagesSchema)
 | 
				
			||||||
      .values({
 | 
					      .values({
 | 
				
			||||||
        content: receivedMessage,
 | 
					        content: recievedMessage,
 | 
				
			||||||
        chatId: chatId,
 | 
					        chatId: chatId,
 | 
				
			||||||
        messageId: aiMessageId,
 | 
					        messageId: aiMessageId,
 | 
				
			||||||
        role: 'assistant',
 | 
					        role: 'assistant',
 | 
				
			||||||
        createdAt: new Date().toString(),
 | 
					        metadata: JSON.stringify({
 | 
				
			||||||
 | 
					          createdAt: new Date(),
 | 
				
			||||||
 | 
					          ...(sources && sources.length > 0 && { sources }),
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      .execute();
 | 
					      .execute();
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
@@ -182,8 +138,6 @@ const handleHistorySave = async (
 | 
				
			|||||||
    where: eq(chats.id, message.chatId),
 | 
					    where: eq(chats.id, message.chatId),
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const fileData = files.map(getFileDetails);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (!chat) {
 | 
					  if (!chat) {
 | 
				
			||||||
    await db
 | 
					    await db
 | 
				
			||||||
      .insert(chats)
 | 
					      .insert(chats)
 | 
				
			||||||
@@ -192,15 +146,9 @@ const handleHistorySave = async (
 | 
				
			|||||||
        title: message.content,
 | 
					        title: message.content,
 | 
				
			||||||
        createdAt: new Date().toString(),
 | 
					        createdAt: new Date().toString(),
 | 
				
			||||||
        focusMode: focusMode,
 | 
					        focusMode: focusMode,
 | 
				
			||||||
        files: fileData,
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
      .execute();
 | 
					 | 
				
			||||||
  } else if (JSON.stringify(chat.files ?? []) != JSON.stringify(fileData)) {
 | 
					 | 
				
			||||||
    db.update(chats)
 | 
					 | 
				
			||||||
      .set({
 | 
					 | 
				
			||||||
        files: files.map(getFileDetails),
 | 
					        files: files.map(getFileDetails),
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      .where(eq(chats.id, message.chatId));
 | 
					      .execute();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const messageExists = await db.query.messages.findFirst({
 | 
					  const messageExists = await db.query.messages.findFirst({
 | 
				
			||||||
@@ -215,7 +163,9 @@ const handleHistorySave = async (
 | 
				
			|||||||
        chatId: message.chatId,
 | 
					        chatId: message.chatId,
 | 
				
			||||||
        messageId: humanMessageId,
 | 
					        messageId: humanMessageId,
 | 
				
			||||||
        role: 'user',
 | 
					        role: 'user',
 | 
				
			||||||
        createdAt: new Date().toString(),
 | 
					        metadata: JSON.stringify({
 | 
				
			||||||
 | 
					          createdAt: new Date(),
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      .execute();
 | 
					      .execute();
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
@@ -233,17 +183,7 @@ const handleHistorySave = async (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export const POST = async (req: Request) => {
 | 
					export const POST = async (req: Request) => {
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
    const reqBody = (await req.json()) as Body;
 | 
					    const body = (await req.json()) as Body;
 | 
				
			||||||
 | 
					 | 
				
			||||||
    const parseBody = safeValidateBody(reqBody);
 | 
					 | 
				
			||||||
    if (!parseBody.success) {
 | 
					 | 
				
			||||||
      return Response.json(
 | 
					 | 
				
			||||||
        { message: 'Invalid request body', error: parseBody.error },
 | 
					 | 
				
			||||||
        { status: 400 },
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const body = parseBody.data as Body;
 | 
					 | 
				
			||||||
    const { message } = body;
 | 
					    const { message } = body;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (message.content === '') {
 | 
					    if (message.content === '') {
 | 
				
			||||||
@@ -255,18 +195,59 @@ export const POST = async (req: Request) => {
 | 
				
			|||||||
      );
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const registry = new ModelRegistry();
 | 
					    const [chatModelProviders, embeddingModelProviders] = await Promise.all([
 | 
				
			||||||
 | 
					      getAvailableChatModelProviders(),
 | 
				
			||||||
    const [llm, embedding] = await Promise.all([
 | 
					      getAvailableEmbeddingModelProviders(),
 | 
				
			||||||
      registry.loadChatModel(body.chatModel.providerId, body.chatModel.key),
 | 
					 | 
				
			||||||
      registry.loadEmbeddingModel(
 | 
					 | 
				
			||||||
        body.embeddingModel.providerId,
 | 
					 | 
				
			||||||
        body.embeddingModel.key,
 | 
					 | 
				
			||||||
      ),
 | 
					 | 
				
			||||||
    ]);
 | 
					    ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const chatModelProvider =
 | 
				
			||||||
 | 
					      chatModelProviders[
 | 
				
			||||||
 | 
					        body.chatModel?.provider || Object.keys(chatModelProviders)[0]
 | 
				
			||||||
 | 
					      ];
 | 
				
			||||||
 | 
					    const chatModel =
 | 
				
			||||||
 | 
					      chatModelProvider[
 | 
				
			||||||
 | 
					        body.chatModel?.name || Object.keys(chatModelProvider)[0]
 | 
				
			||||||
 | 
					      ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const embeddingProvider =
 | 
				
			||||||
 | 
					      embeddingModelProviders[
 | 
				
			||||||
 | 
					        body.embeddingModel?.provider || Object.keys(embeddingModelProviders)[0]
 | 
				
			||||||
 | 
					      ];
 | 
				
			||||||
 | 
					    const embeddingModel =
 | 
				
			||||||
 | 
					      embeddingProvider[
 | 
				
			||||||
 | 
					        body.embeddingModel?.name || Object.keys(embeddingProvider)[0]
 | 
				
			||||||
 | 
					      ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let llm: BaseChatModel | undefined;
 | 
				
			||||||
 | 
					    let embedding = embeddingModel.model;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (body.chatModel?.provider === 'custom_openai') {
 | 
				
			||||||
 | 
					      llm = new ChatOpenAI({
 | 
				
			||||||
 | 
					        openAIApiKey: getCustomOpenaiApiKey(),
 | 
				
			||||||
 | 
					        modelName: getCustomOpenaiModelName(),
 | 
				
			||||||
 | 
					        temperature: 0.7,
 | 
				
			||||||
 | 
					        configuration: {
 | 
				
			||||||
 | 
					          baseURL: getCustomOpenaiApiUrl(),
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      }) as unknown as BaseChatModel;
 | 
				
			||||||
 | 
					    } else if (chatModelProvider && chatModel) {
 | 
				
			||||||
 | 
					      llm = chatModel.model;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!llm) {
 | 
				
			||||||
 | 
					      return Response.json({ error: 'Invalid chat model' }, { status: 400 });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!embedding) {
 | 
				
			||||||
 | 
					      return Response.json(
 | 
				
			||||||
 | 
					        { error: 'Invalid embedding model' },
 | 
				
			||||||
 | 
					        { status: 400 },
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const humanMessageId =
 | 
					    const humanMessageId =
 | 
				
			||||||
      message.messageId ?? crypto.randomBytes(7).toString('hex');
 | 
					      message.messageId ?? crypto.randomBytes(7).toString('hex');
 | 
				
			||||||
 | 
					    const aiMessageId = crypto.randomBytes(7).toString('hex');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const history: BaseMessage[] = body.history.map((msg) => {
 | 
					    const history: BaseMessage[] = body.history.map((msg) => {
 | 
				
			||||||
      if (msg[0] === 'human') {
 | 
					      if (msg[0] === 'human') {
 | 
				
			||||||
@@ -298,14 +279,14 @@ export const POST = async (req: Request) => {
 | 
				
			|||||||
      embedding,
 | 
					      embedding,
 | 
				
			||||||
      body.optimizationMode,
 | 
					      body.optimizationMode,
 | 
				
			||||||
      body.files,
 | 
					      body.files,
 | 
				
			||||||
      body.systemInstructions as string,
 | 
					      body.systemInstructions,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const responseStream = new TransformStream();
 | 
					    const responseStream = new TransformStream();
 | 
				
			||||||
    const writer = responseStream.writable.getWriter();
 | 
					    const writer = responseStream.writable.getWriter();
 | 
				
			||||||
    const encoder = new TextEncoder();
 | 
					    const encoder = new TextEncoder();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    handleEmitterEvents(stream, writer, encoder, message.chatId);
 | 
					    handleEmitterEvents(stream, writer, encoder, aiMessageId, message.chatId);
 | 
				
			||||||
    handleHistorySave(message, humanMessageId, body.focusMode, body.files);
 | 
					    handleHistorySave(message, humanMessageId, body.focusMode, body.files);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return new Response(responseStream.readable, {
 | 
					    return new Response(responseStream.readable, {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,76 +1,113 @@
 | 
				
			|||||||
import configManager from '@/lib/config';
 | 
					import {
 | 
				
			||||||
import ModelRegistry from '@/lib/models/registry';
 | 
					  getAnthropicApiKey,
 | 
				
			||||||
import { NextRequest, NextResponse } from 'next/server';
 | 
					  getCustomOpenaiApiKey,
 | 
				
			||||||
import { ConfigModelProvider } from '@/lib/config/types';
 | 
					  getCustomOpenaiApiUrl,
 | 
				
			||||||
 | 
					  getCustomOpenaiModelName,
 | 
				
			||||||
 | 
					  getGeminiApiKey,
 | 
				
			||||||
 | 
					  getGroqApiKey,
 | 
				
			||||||
 | 
					  getOllamaApiEndpoint,
 | 
				
			||||||
 | 
					  getOpenaiApiKey,
 | 
				
			||||||
 | 
					  getDeepseekApiKey,
 | 
				
			||||||
 | 
					  updateConfig,
 | 
				
			||||||
 | 
					} from '@/lib/config';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  getAvailableChatModelProviders,
 | 
				
			||||||
 | 
					  getAvailableEmbeddingModelProviders,
 | 
				
			||||||
 | 
					} from '@/lib/providers';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type SaveConfigBody = {
 | 
					export const GET = async (req: Request) => {
 | 
				
			||||||
  key: string;
 | 
					 | 
				
			||||||
  value: string;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const GET = async (req: NextRequest) => {
 | 
					 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
    const values = configManager.getCurrentConfig();
 | 
					    const config: Record<string, any> = {};
 | 
				
			||||||
    const fields = configManager.getUIConfigSections();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const modelRegistry = new ModelRegistry();
 | 
					    const [chatModelProviders, embeddingModelProviders] = await Promise.all([
 | 
				
			||||||
    const modelProviders = await modelRegistry.getActiveProviders();
 | 
					      getAvailableChatModelProviders(),
 | 
				
			||||||
 | 
					      getAvailableEmbeddingModelProviders(),
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    values.modelProviders = values.modelProviders.map(
 | 
					    config['chatModelProviders'] = {};
 | 
				
			||||||
      (mp: ConfigModelProvider) => {
 | 
					    config['embeddingModelProviders'] = {};
 | 
				
			||||||
        const activeProvider = modelProviders.find((p) => p.id === mp.id);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (const provider in chatModelProviders) {
 | 
				
			||||||
 | 
					      config['chatModelProviders'][provider] = Object.keys(
 | 
				
			||||||
 | 
					        chatModelProviders[provider],
 | 
				
			||||||
 | 
					      ).map((model) => {
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
          ...mp,
 | 
					          name: model,
 | 
				
			||||||
          chatModels: activeProvider?.chatModels ?? mp.chatModels,
 | 
					          displayName: chatModelProviders[provider][model].displayName,
 | 
				
			||||||
          embeddingModels:
 | 
					 | 
				
			||||||
            activeProvider?.embeddingModels ?? mp.embeddingModels,
 | 
					 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
      },
 | 
					      });
 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return NextResponse.json({
 | 
					 | 
				
			||||||
      values,
 | 
					 | 
				
			||||||
      fields,
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  } catch (err) {
 | 
					 | 
				
			||||||
    console.error('Error in getting config: ', err);
 | 
					 | 
				
			||||||
    return Response.json(
 | 
					 | 
				
			||||||
      { message: 'An error has occurred.' },
 | 
					 | 
				
			||||||
      { status: 500 },
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const POST = async (req: NextRequest) => {
 | 
					 | 
				
			||||||
  try {
 | 
					 | 
				
			||||||
    const body: SaveConfigBody = await req.json();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!body.key || !body.value) {
 | 
					 | 
				
			||||||
      return Response.json(
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          message: 'Key and value are required.',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          status: 400,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    configManager.updateConfig(body.key, body.value);
 | 
					    for (const provider in embeddingModelProviders) {
 | 
				
			||||||
 | 
					      config['embeddingModelProviders'][provider] = Object.keys(
 | 
				
			||||||
 | 
					        embeddingModelProviders[provider],
 | 
				
			||||||
 | 
					      ).map((model) => {
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					          name: model,
 | 
				
			||||||
 | 
					          displayName: embeddingModelProviders[provider][model].displayName,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return Response.json(
 | 
					    config['openaiApiKey'] = getOpenaiApiKey();
 | 
				
			||||||
      {
 | 
					    config['ollamaApiUrl'] = getOllamaApiEndpoint();
 | 
				
			||||||
        message: 'Config updated successfully.',
 | 
					    config['anthropicApiKey'] = getAnthropicApiKey();
 | 
				
			||||||
      },
 | 
					    config['groqApiKey'] = getGroqApiKey();
 | 
				
			||||||
      {
 | 
					    config['geminiApiKey'] = getGeminiApiKey();
 | 
				
			||||||
        status: 200,
 | 
					    config['deepseekApiKey'] = getDeepseekApiKey();
 | 
				
			||||||
      },
 | 
					    config['customOpenaiApiUrl'] = getCustomOpenaiApiUrl();
 | 
				
			||||||
    );
 | 
					    config['customOpenaiApiKey'] = getCustomOpenaiApiKey();
 | 
				
			||||||
 | 
					    config['customOpenaiModelName'] = getCustomOpenaiModelName();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return Response.json({ ...config }, { status: 200 });
 | 
				
			||||||
  } catch (err) {
 | 
					  } catch (err) {
 | 
				
			||||||
    console.error('Error in getting config: ', err);
 | 
					    console.error('An error occurred while getting config:', err);
 | 
				
			||||||
    return Response.json(
 | 
					    return Response.json(
 | 
				
			||||||
      { message: 'An error has occurred.' },
 | 
					      { message: 'An error occurred while getting config' },
 | 
				
			||||||
 | 
					      { status: 500 },
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const POST = async (req: Request) => {
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    const config = await req.json();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const updatedConfig = {
 | 
				
			||||||
 | 
					      MODELS: {
 | 
				
			||||||
 | 
					        OPENAI: {
 | 
				
			||||||
 | 
					          API_KEY: config.openaiApiKey,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        GROQ: {
 | 
				
			||||||
 | 
					          API_KEY: config.groqApiKey,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        ANTHROPIC: {
 | 
				
			||||||
 | 
					          API_KEY: config.anthropicApiKey,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        GEMINI: {
 | 
				
			||||||
 | 
					          API_KEY: config.geminiApiKey,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        OLLAMA: {
 | 
				
			||||||
 | 
					          API_URL: config.ollamaApiUrl,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        DEEPSEEK: {
 | 
				
			||||||
 | 
					          API_KEY: config.deepseekApiKey,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        CUSTOM_OPENAI: {
 | 
				
			||||||
 | 
					          API_URL: config.customOpenaiApiUrl,
 | 
				
			||||||
 | 
					          API_KEY: config.customOpenaiApiKey,
 | 
				
			||||||
 | 
					          MODEL_NAME: config.customOpenaiModelName,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    updateConfig(updatedConfig);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return Response.json({ message: 'Config updated' }, { status: 200 });
 | 
				
			||||||
 | 
					  } catch (err) {
 | 
				
			||||||
 | 
					    console.error('An error occurred while updating config:', err);
 | 
				
			||||||
 | 
					    return Response.json(
 | 
				
			||||||
 | 
					      { message: 'An error occurred while updating config' },
 | 
				
			||||||
      { status: 500 },
 | 
					      { status: 500 },
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,23 +0,0 @@
 | 
				
			|||||||
import configManager from '@/lib/config';
 | 
					 | 
				
			||||||
import { NextRequest } from 'next/server';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const POST = async (req: NextRequest) => {
 | 
					 | 
				
			||||||
  try {
 | 
					 | 
				
			||||||
    configManager.markSetupComplete();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return Response.json(
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        message: 'Setup marked as complete.',
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        status: 200,
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  } catch (err) {
 | 
					 | 
				
			||||||
    console.error('Error marking setup as complete: ', err);
 | 
					 | 
				
			||||||
    return Response.json(
 | 
					 | 
				
			||||||
      { message: 'An error has occurred.' },
 | 
					 | 
				
			||||||
      { status: 500 },
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
@@ -1,80 +1,43 @@
 | 
				
			|||||||
import { searchSearxng } from '@/lib/searxng';
 | 
					import { searchSearxng } from '@/lib/searxng';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const websitesForTopic = {
 | 
					const articleWebsites = [
 | 
				
			||||||
  tech: {
 | 
					  'yahoo.com',
 | 
				
			||||||
    query: ['technology news', 'latest tech', 'AI', 'science and innovation'],
 | 
					  'www.exchangewire.com',
 | 
				
			||||||
    links: ['techcrunch.com', 'wired.com', 'theverge.com'],
 | 
					  'businessinsider.com',
 | 
				
			||||||
  },
 | 
					  /* 'wired.com',
 | 
				
			||||||
  finance: {
 | 
					  'mashable.com',
 | 
				
			||||||
    query: ['finance news', 'economy', 'stock market', 'investing'],
 | 
					  'theverge.com',
 | 
				
			||||||
    links: ['bloomberg.com', 'cnbc.com', 'marketwatch.com'],
 | 
					  'gizmodo.com',
 | 
				
			||||||
  },
 | 
					  'cnet.com',
 | 
				
			||||||
  art: {
 | 
					  'venturebeat.com', */
 | 
				
			||||||
    query: ['art news', 'culture', 'modern art', 'cultural events'],
 | 
					];
 | 
				
			||||||
    links: ['artnews.com', 'hyperallergic.com', 'theartnewspaper.com'],
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  sports: {
 | 
					 | 
				
			||||||
    query: ['sports news', 'latest sports', 'cricket football tennis'],
 | 
					 | 
				
			||||||
    links: ['espn.com', 'bbc.com/sport', 'skysports.com'],
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  entertainment: {
 | 
					 | 
				
			||||||
    query: ['entertainment news', 'movies', 'TV shows', 'celebrities'],
 | 
					 | 
				
			||||||
    links: ['hollywoodreporter.com', 'variety.com', 'deadline.com'],
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Topic = keyof typeof websitesForTopic;
 | 
					const topics = ['AI', 'tech']; /* TODO: Add UI to customize this */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const GET = async (req: Request) => {
 | 
					export const GET = async (req: Request) => {
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
    const params = new URL(req.url).searchParams;
 | 
					    const data = (
 | 
				
			||||||
 | 
					      await Promise.all([
 | 
				
			||||||
    const mode: 'normal' | 'preview' =
 | 
					        ...new Array(articleWebsites.length * topics.length)
 | 
				
			||||||
      (params.get('mode') as 'normal' | 'preview') || 'normal';
 | 
					          .fill(0)
 | 
				
			||||||
    const topic: Topic = (params.get('topic') as Topic) || 'tech';
 | 
					          .map(async (_, i) => {
 | 
				
			||||||
 | 
					            return (
 | 
				
			||||||
    const selectedTopic = websitesForTopic[topic];
 | 
					              await searchSearxng(
 | 
				
			||||||
 | 
					                `site:${articleWebsites[i % articleWebsites.length]} ${
 | 
				
			||||||
    let data = [];
 | 
					                  topics[i % topics.length]
 | 
				
			||||||
 | 
					                }`,
 | 
				
			||||||
    if (mode === 'normal') {
 | 
					                {
 | 
				
			||||||
      const seenUrls = new Set();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      data = (
 | 
					 | 
				
			||||||
        await Promise.all(
 | 
					 | 
				
			||||||
          selectedTopic.links.flatMap((link) =>
 | 
					 | 
				
			||||||
            selectedTopic.query.map(async (query) => {
 | 
					 | 
				
			||||||
              return (
 | 
					 | 
				
			||||||
                await searchSearxng(`site:${link} ${query}`, {
 | 
					 | 
				
			||||||
                  engines: ['bing news'],
 | 
					                  engines: ['bing news'],
 | 
				
			||||||
                  pageno: 1,
 | 
					                  pageno: 1,
 | 
				
			||||||
                  language: 'en',
 | 
					                },
 | 
				
			||||||
                })
 | 
					              )
 | 
				
			||||||
              ).results;
 | 
					            ).results;
 | 
				
			||||||
            }),
 | 
					          }),
 | 
				
			||||||
          ),
 | 
					      ])
 | 
				
			||||||
        )
 | 
					    )
 | 
				
			||||||
      )
 | 
					      .map((result) => result)
 | 
				
			||||||
        .flat()
 | 
					      .flat()
 | 
				
			||||||
        .filter((item) => {
 | 
					      .sort(() => Math.random() - 0.5);
 | 
				
			||||||
          const url = item.url?.toLowerCase().trim();
 | 
					 | 
				
			||||||
          if (seenUrls.has(url)) return false;
 | 
					 | 
				
			||||||
          seenUrls.add(url);
 | 
					 | 
				
			||||||
          return true;
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
        .sort(() => Math.random() - 0.5);
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      data = (
 | 
					 | 
				
			||||||
        await searchSearxng(
 | 
					 | 
				
			||||||
          `site:${selectedTopic.links[Math.floor(Math.random() * selectedTopic.links.length)]} ${selectedTopic.query[Math.floor(Math.random() * selectedTopic.query.length)]}`,
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            engines: ['bing news'],
 | 
					 | 
				
			||||||
            pageno: 1,
 | 
					 | 
				
			||||||
            language: 'en',
 | 
					 | 
				
			||||||
          },
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
      ).results;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return Response.json(
 | 
					    return Response.json(
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,12 +1,23 @@
 | 
				
			|||||||
import handleImageSearch from '@/lib/chains/imageSearchAgent';
 | 
					import handleImageSearch from '@/lib/chains/imageSearchAgent';
 | 
				
			||||||
import ModelRegistry from '@/lib/models/registry';
 | 
					import {
 | 
				
			||||||
import { ModelWithProvider } from '@/lib/models/types';
 | 
					  getCustomOpenaiApiKey,
 | 
				
			||||||
 | 
					  getCustomOpenaiApiUrl,
 | 
				
			||||||
 | 
					  getCustomOpenaiModelName,
 | 
				
			||||||
 | 
					} from '@/lib/config';
 | 
				
			||||||
 | 
					import { getAvailableChatModelProviders } from '@/lib/providers';
 | 
				
			||||||
 | 
					import { BaseChatModel } from '@langchain/core/language_models/chat_models';
 | 
				
			||||||
import { AIMessage, BaseMessage, HumanMessage } from '@langchain/core/messages';
 | 
					import { AIMessage, BaseMessage, HumanMessage } from '@langchain/core/messages';
 | 
				
			||||||
 | 
					import { ChatOpenAI } from '@langchain/openai';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface ChatModel {
 | 
				
			||||||
 | 
					  provider: string;
 | 
				
			||||||
 | 
					  model: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface ImageSearchBody {
 | 
					interface ImageSearchBody {
 | 
				
			||||||
  query: string;
 | 
					  query: string;
 | 
				
			||||||
  chatHistory: any[];
 | 
					  chatHistory: any[];
 | 
				
			||||||
  chatModel: ModelWithProvider;
 | 
					  chatModel?: ChatModel;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const POST = async (req: Request) => {
 | 
					export const POST = async (req: Request) => {
 | 
				
			||||||
@@ -23,12 +34,35 @@ export const POST = async (req: Request) => {
 | 
				
			|||||||
      })
 | 
					      })
 | 
				
			||||||
      .filter((msg) => msg !== undefined) as BaseMessage[];
 | 
					      .filter((msg) => msg !== undefined) as BaseMessage[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const registry = new ModelRegistry();
 | 
					    const chatModelProviders = await getAvailableChatModelProviders();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const llm = await registry.loadChatModel(
 | 
					    const chatModelProvider =
 | 
				
			||||||
      body.chatModel.providerId,
 | 
					      chatModelProviders[
 | 
				
			||||||
      body.chatModel.key,
 | 
					        body.chatModel?.provider || Object.keys(chatModelProviders)[0]
 | 
				
			||||||
    );
 | 
					      ];
 | 
				
			||||||
 | 
					    const chatModel =
 | 
				
			||||||
 | 
					      chatModelProvider[
 | 
				
			||||||
 | 
					        body.chatModel?.model || Object.keys(chatModelProvider)[0]
 | 
				
			||||||
 | 
					      ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let llm: BaseChatModel | undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (body.chatModel?.provider === 'custom_openai') {
 | 
				
			||||||
 | 
					      llm = new ChatOpenAI({
 | 
				
			||||||
 | 
					        openAIApiKey: getCustomOpenaiApiKey(),
 | 
				
			||||||
 | 
					        modelName: getCustomOpenaiModelName(),
 | 
				
			||||||
 | 
					        temperature: 0.7,
 | 
				
			||||||
 | 
					        configuration: {
 | 
				
			||||||
 | 
					          baseURL: getCustomOpenaiApiUrl(),
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      }) as unknown as BaseChatModel;
 | 
				
			||||||
 | 
					    } else if (chatModelProvider && chatModel) {
 | 
				
			||||||
 | 
					      llm = chatModel.model;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!llm) {
 | 
				
			||||||
 | 
					      return Response.json({ error: 'Invalid chat model' }, { status: 400 });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const images = await handleImageSearch(
 | 
					    const images = await handleImageSearch(
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										47
									
								
								src/app/api/models/route.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,47 @@
 | 
				
			|||||||
 | 
					import {
 | 
				
			||||||
 | 
					  getAvailableChatModelProviders,
 | 
				
			||||||
 | 
					  getAvailableEmbeddingModelProviders,
 | 
				
			||||||
 | 
					} from '@/lib/providers';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const GET = async (req: Request) => {
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    const [chatModelProviders, embeddingModelProviders] = await Promise.all([
 | 
				
			||||||
 | 
					      getAvailableChatModelProviders(),
 | 
				
			||||||
 | 
					      getAvailableEmbeddingModelProviders(),
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Object.keys(chatModelProviders).forEach((provider) => {
 | 
				
			||||||
 | 
					      Object.keys(chatModelProviders[provider]).forEach((model) => {
 | 
				
			||||||
 | 
					        delete (chatModelProviders[provider][model] as { model?: unknown })
 | 
				
			||||||
 | 
					          .model;
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Object.keys(embeddingModelProviders).forEach((provider) => {
 | 
				
			||||||
 | 
					      Object.keys(embeddingModelProviders[provider]).forEach((model) => {
 | 
				
			||||||
 | 
					        delete (embeddingModelProviders[provider][model] as { model?: unknown })
 | 
				
			||||||
 | 
					          .model;
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return Response.json(
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        chatModelProviders,
 | 
				
			||||||
 | 
					        embeddingModelProviders,
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        status: 200,
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  } catch (err) {
 | 
				
			||||||
 | 
					    console.error('An error occurred while fetching models', err);
 | 
				
			||||||
 | 
					    return Response.json(
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        message: 'An error has occurred.',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        status: 500,
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@@ -1,94 +0,0 @@
 | 
				
			|||||||
import ModelRegistry from '@/lib/models/registry';
 | 
					 | 
				
			||||||
import { Model } from '@/lib/models/types';
 | 
					 | 
				
			||||||
import { NextRequest } from 'next/server';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const POST = async (
 | 
					 | 
				
			||||||
  req: NextRequest,
 | 
					 | 
				
			||||||
  { params }: { params: Promise<{ id: string }> },
 | 
					 | 
				
			||||||
) => {
 | 
					 | 
				
			||||||
  try {
 | 
					 | 
				
			||||||
    const { id } = await params;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const body: Partial<Model> & { type: 'embedding' | 'chat' } =
 | 
					 | 
				
			||||||
      await req.json();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!body.key || !body.name) {
 | 
					 | 
				
			||||||
      return Response.json(
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          message: 'Key and name must be provided',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          status: 400,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const registry = new ModelRegistry();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await registry.addProviderModel(id, body.type, body);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return Response.json(
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        message: 'Model added successfully',
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        status: 200,
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  } catch (err) {
 | 
					 | 
				
			||||||
    console.error('An error occurred while adding provider model', err);
 | 
					 | 
				
			||||||
    return Response.json(
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        message: 'An error has occurred.',
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        status: 500,
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const DELETE = async (
 | 
					 | 
				
			||||||
  req: NextRequest,
 | 
					 | 
				
			||||||
  { params }: { params: Promise<{ id: string }> },
 | 
					 | 
				
			||||||
) => {
 | 
					 | 
				
			||||||
  try {
 | 
					 | 
				
			||||||
    const { id } = await params;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const body: { key: string; type: 'embedding' | 'chat' } = await req.json();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!body.key) {
 | 
					 | 
				
			||||||
      return Response.json(
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          message: 'Key and name must be provided',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          status: 400,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const registry = new ModelRegistry();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await registry.removeProviderModel(id, body.type, body.key);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return Response.json(
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        message: 'Model added successfully',
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        status: 200,
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  } catch (err) {
 | 
					 | 
				
			||||||
    console.error('An error occurred while deleting provider model', err);
 | 
					 | 
				
			||||||
    return Response.json(
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        message: 'An error has occurred.',
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        status: 500,
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
@@ -1,89 +0,0 @@
 | 
				
			|||||||
import ModelRegistry from '@/lib/models/registry';
 | 
					 | 
				
			||||||
import { NextRequest } from 'next/server';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const DELETE = async (
 | 
					 | 
				
			||||||
  req: NextRequest,
 | 
					 | 
				
			||||||
  { params }: { params: Promise<{ id: string }> },
 | 
					 | 
				
			||||||
) => {
 | 
					 | 
				
			||||||
  try {
 | 
					 | 
				
			||||||
    const { id } = await params;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!id) {
 | 
					 | 
				
			||||||
      return Response.json(
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          message: 'Provider ID is required.',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          status: 400,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const registry = new ModelRegistry();
 | 
					 | 
				
			||||||
    await registry.removeProvider(id);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return Response.json(
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        message: 'Provider deleted successfully.',
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        status: 200,
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  } catch (err: any) {
 | 
					 | 
				
			||||||
    console.error('An error occurred while deleting provider', err.message);
 | 
					 | 
				
			||||||
    return Response.json(
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        message: 'An error has occurred.',
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        status: 500,
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const PATCH = async (
 | 
					 | 
				
			||||||
  req: NextRequest,
 | 
					 | 
				
			||||||
  { params }: { params: Promise<{ id: string }> },
 | 
					 | 
				
			||||||
) => {
 | 
					 | 
				
			||||||
  try {
 | 
					 | 
				
			||||||
    const body = await req.json();
 | 
					 | 
				
			||||||
    const { name, config } = body;
 | 
					 | 
				
			||||||
    const { id } = await params;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!id || !name || !config) {
 | 
					 | 
				
			||||||
      return Response.json(
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          message: 'Missing required fields.',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          status: 400,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const registry = new ModelRegistry();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const updatedProvider = await registry.updateProvider(id, name, config);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return Response.json(
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        provider: updatedProvider,
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        status: 200,
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  } catch (err: any) {
 | 
					 | 
				
			||||||
    console.error('An error occurred while updating provider', err.message);
 | 
					 | 
				
			||||||
    return Response.json(
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        message: 'An error has occurred.',
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        status: 500,
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
@@ -1,74 +0,0 @@
 | 
				
			|||||||
import ModelRegistry from '@/lib/models/registry';
 | 
					 | 
				
			||||||
import { NextRequest } from 'next/server';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const GET = async (req: Request) => {
 | 
					 | 
				
			||||||
  try {
 | 
					 | 
				
			||||||
    const registry = new ModelRegistry();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const activeProviders = await registry.getActiveProviders();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const filteredProviders = activeProviders.filter((p) => {
 | 
					 | 
				
			||||||
      return !p.chatModels.some((m) => m.key === 'error');
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return Response.json(
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        providers: filteredProviders,
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        status: 200,
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  } catch (err) {
 | 
					 | 
				
			||||||
    console.error('An error occurred while fetching providers', err);
 | 
					 | 
				
			||||||
    return Response.json(
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        message: 'An error has occurred.',
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        status: 500,
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const POST = async (req: NextRequest) => {
 | 
					 | 
				
			||||||
  try {
 | 
					 | 
				
			||||||
    const body = await req.json();
 | 
					 | 
				
			||||||
    const { type, name, config } = body;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!type || !name || !config) {
 | 
					 | 
				
			||||||
      return Response.json(
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          message: 'Missing required fields.',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          status: 400,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const registry = new ModelRegistry();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const newProvider = await registry.addProvider(type, name, config);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return Response.json(
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        provider: newProvider,
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        status: 200,
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  } catch (err) {
 | 
					 | 
				
			||||||
    console.error('An error occurred while creating provider', err);
 | 
					 | 
				
			||||||
    return Response.json(
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        message: 'An error has occurred.',
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        status: 500,
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
@@ -1,14 +1,36 @@
 | 
				
			|||||||
 | 
					import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
 | 
				
			||||||
 | 
					import type { Embeddings } from '@langchain/core/embeddings';
 | 
				
			||||||
 | 
					import { ChatOpenAI } from '@langchain/openai';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  getAvailableChatModelProviders,
 | 
				
			||||||
 | 
					  getAvailableEmbeddingModelProviders,
 | 
				
			||||||
 | 
					} from '@/lib/providers';
 | 
				
			||||||
import { AIMessage, BaseMessage, HumanMessage } from '@langchain/core/messages';
 | 
					import { AIMessage, BaseMessage, HumanMessage } from '@langchain/core/messages';
 | 
				
			||||||
import { MetaSearchAgentType } from '@/lib/search/metaSearchAgent';
 | 
					import { MetaSearchAgentType } from '@/lib/search/metaSearchAgent';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  getCustomOpenaiApiKey,
 | 
				
			||||||
 | 
					  getCustomOpenaiApiUrl,
 | 
				
			||||||
 | 
					  getCustomOpenaiModelName,
 | 
				
			||||||
 | 
					} from '@/lib/config';
 | 
				
			||||||
import { searchHandlers } from '@/lib/search';
 | 
					import { searchHandlers } from '@/lib/search';
 | 
				
			||||||
import ModelRegistry from '@/lib/models/registry';
 | 
					
 | 
				
			||||||
import { ModelWithProvider } from '@/lib/models/types';
 | 
					interface chatModel {
 | 
				
			||||||
 | 
					  provider: string;
 | 
				
			||||||
 | 
					  name: string;
 | 
				
			||||||
 | 
					  customOpenAIKey?: string;
 | 
				
			||||||
 | 
					  customOpenAIBaseURL?: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface embeddingModel {
 | 
				
			||||||
 | 
					  provider: string;
 | 
				
			||||||
 | 
					  name: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface ChatRequestBody {
 | 
					interface ChatRequestBody {
 | 
				
			||||||
  optimizationMode: 'speed' | 'balanced';
 | 
					  optimizationMode: 'speed' | 'balanced';
 | 
				
			||||||
  focusMode: string;
 | 
					  focusMode: string;
 | 
				
			||||||
  chatModel: ModelWithProvider;
 | 
					  chatModel?: chatModel;
 | 
				
			||||||
  embeddingModel: ModelWithProvider;
 | 
					  embeddingModel?: embeddingModel;
 | 
				
			||||||
  query: string;
 | 
					  query: string;
 | 
				
			||||||
  history: Array<[string, string]>;
 | 
					  history: Array<[string, string]>;
 | 
				
			||||||
  stream?: boolean;
 | 
					  stream?: boolean;
 | 
				
			||||||
@@ -36,16 +58,61 @@ export const POST = async (req: Request) => {
 | 
				
			|||||||
        : new AIMessage({ content: msg[1] });
 | 
					        : new AIMessage({ content: msg[1] });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const registry = new ModelRegistry();
 | 
					    const [chatModelProviders, embeddingModelProviders] = await Promise.all([
 | 
				
			||||||
 | 
					      getAvailableChatModelProviders(),
 | 
				
			||||||
    const [llm, embeddings] = await Promise.all([
 | 
					      getAvailableEmbeddingModelProviders(),
 | 
				
			||||||
      registry.loadChatModel(body.chatModel.providerId, body.chatModel.key),
 | 
					 | 
				
			||||||
      registry.loadEmbeddingModel(
 | 
					 | 
				
			||||||
        body.embeddingModel.providerId,
 | 
					 | 
				
			||||||
        body.embeddingModel.key,
 | 
					 | 
				
			||||||
      ),
 | 
					 | 
				
			||||||
    ]);
 | 
					    ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const chatModelProvider =
 | 
				
			||||||
 | 
					      body.chatModel?.provider || Object.keys(chatModelProviders)[0];
 | 
				
			||||||
 | 
					    const chatModel =
 | 
				
			||||||
 | 
					      body.chatModel?.name ||
 | 
				
			||||||
 | 
					      Object.keys(chatModelProviders[chatModelProvider])[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const embeddingModelProvider =
 | 
				
			||||||
 | 
					      body.embeddingModel?.provider || Object.keys(embeddingModelProviders)[0];
 | 
				
			||||||
 | 
					    const embeddingModel =
 | 
				
			||||||
 | 
					      body.embeddingModel?.name ||
 | 
				
			||||||
 | 
					      Object.keys(embeddingModelProviders[embeddingModelProvider])[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let llm: BaseChatModel | undefined;
 | 
				
			||||||
 | 
					    let embeddings: Embeddings | undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (body.chatModel?.provider === 'custom_openai') {
 | 
				
			||||||
 | 
					      llm = new ChatOpenAI({
 | 
				
			||||||
 | 
					        modelName: body.chatModel?.name || getCustomOpenaiModelName(),
 | 
				
			||||||
 | 
					        openAIApiKey:
 | 
				
			||||||
 | 
					          body.chatModel?.customOpenAIKey || getCustomOpenaiApiKey(),
 | 
				
			||||||
 | 
					        temperature: 0.7,
 | 
				
			||||||
 | 
					        configuration: {
 | 
				
			||||||
 | 
					          baseURL:
 | 
				
			||||||
 | 
					            body.chatModel?.customOpenAIBaseURL || getCustomOpenaiApiUrl(),
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      }) as unknown as BaseChatModel;
 | 
				
			||||||
 | 
					    } else if (
 | 
				
			||||||
 | 
					      chatModelProviders[chatModelProvider] &&
 | 
				
			||||||
 | 
					      chatModelProviders[chatModelProvider][chatModel]
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					      llm = chatModelProviders[chatModelProvider][chatModel]
 | 
				
			||||||
 | 
					        .model as unknown as BaseChatModel | undefined;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (
 | 
				
			||||||
 | 
					      embeddingModelProviders[embeddingModelProvider] &&
 | 
				
			||||||
 | 
					      embeddingModelProviders[embeddingModelProvider][embeddingModel]
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					      embeddings = embeddingModelProviders[embeddingModelProvider][
 | 
				
			||||||
 | 
					        embeddingModel
 | 
				
			||||||
 | 
					      ].model as Embeddings | undefined;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!llm || !embeddings) {
 | 
				
			||||||
 | 
					      return Response.json(
 | 
				
			||||||
 | 
					        { message: 'Invalid model selected' },
 | 
				
			||||||
 | 
					        { status: 400 },
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const searchHandler: MetaSearchAgentType = searchHandlers[body.focusMode];
 | 
					    const searchHandler: MetaSearchAgentType = searchHandlers[body.focusMode];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!searchHandler) {
 | 
					    if (!searchHandler) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,12 +1,22 @@
 | 
				
			|||||||
import generateSuggestions from '@/lib/chains/suggestionGeneratorAgent';
 | 
					import generateSuggestions from '@/lib/chains/suggestionGeneratorAgent';
 | 
				
			||||||
import ModelRegistry from '@/lib/models/registry';
 | 
					import {
 | 
				
			||||||
import { ModelWithProvider } from '@/lib/models/types';
 | 
					  getCustomOpenaiApiKey,
 | 
				
			||||||
 | 
					  getCustomOpenaiApiUrl,
 | 
				
			||||||
 | 
					  getCustomOpenaiModelName,
 | 
				
			||||||
 | 
					} from '@/lib/config';
 | 
				
			||||||
 | 
					import { getAvailableChatModelProviders } from '@/lib/providers';
 | 
				
			||||||
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
 | 
					import { BaseChatModel } from '@langchain/core/language_models/chat_models';
 | 
				
			||||||
import { AIMessage, BaseMessage, HumanMessage } from '@langchain/core/messages';
 | 
					import { AIMessage, BaseMessage, HumanMessage } from '@langchain/core/messages';
 | 
				
			||||||
 | 
					import { ChatOpenAI } from '@langchain/openai';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface ChatModel {
 | 
				
			||||||
 | 
					  provider: string;
 | 
				
			||||||
 | 
					  model: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface SuggestionsGenerationBody {
 | 
					interface SuggestionsGenerationBody {
 | 
				
			||||||
  chatHistory: any[];
 | 
					  chatHistory: any[];
 | 
				
			||||||
  chatModel: ModelWithProvider;
 | 
					  chatModel?: ChatModel;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const POST = async (req: Request) => {
 | 
					export const POST = async (req: Request) => {
 | 
				
			||||||
@@ -23,12 +33,35 @@ export const POST = async (req: Request) => {
 | 
				
			|||||||
      })
 | 
					      })
 | 
				
			||||||
      .filter((msg) => msg !== undefined) as BaseMessage[];
 | 
					      .filter((msg) => msg !== undefined) as BaseMessage[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const registry = new ModelRegistry();
 | 
					    const chatModelProviders = await getAvailableChatModelProviders();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const llm = await registry.loadChatModel(
 | 
					    const chatModelProvider =
 | 
				
			||||||
      body.chatModel.providerId,
 | 
					      chatModelProviders[
 | 
				
			||||||
      body.chatModel.key,
 | 
					        body.chatModel?.provider || Object.keys(chatModelProviders)[0]
 | 
				
			||||||
    );
 | 
					      ];
 | 
				
			||||||
 | 
					    const chatModel =
 | 
				
			||||||
 | 
					      chatModelProvider[
 | 
				
			||||||
 | 
					        body.chatModel?.model || Object.keys(chatModelProvider)[0]
 | 
				
			||||||
 | 
					      ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let llm: BaseChatModel | undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (body.chatModel?.provider === 'custom_openai') {
 | 
				
			||||||
 | 
					      llm = new ChatOpenAI({
 | 
				
			||||||
 | 
					        openAIApiKey: getCustomOpenaiApiKey(),
 | 
				
			||||||
 | 
					        modelName: getCustomOpenaiModelName(),
 | 
				
			||||||
 | 
					        temperature: 0.7,
 | 
				
			||||||
 | 
					        configuration: {
 | 
				
			||||||
 | 
					          baseURL: getCustomOpenaiApiUrl(),
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      }) as unknown as BaseChatModel;
 | 
				
			||||||
 | 
					    } else if (chatModelProvider && chatModel) {
 | 
				
			||||||
 | 
					      llm = chatModel.model;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!llm) {
 | 
				
			||||||
 | 
					      return Response.json({ error: 'Invalid chat model' }, { status: 400 });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const suggestions = await generateSuggestions(
 | 
					    const suggestions = await generateSuggestions(
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,11 +2,11 @@ import { NextResponse } from 'next/server';
 | 
				
			|||||||
import fs from 'fs';
 | 
					import fs from 'fs';
 | 
				
			||||||
import path from 'path';
 | 
					import path from 'path';
 | 
				
			||||||
import crypto from 'crypto';
 | 
					import crypto from 'crypto';
 | 
				
			||||||
 | 
					import { getAvailableEmbeddingModelProviders } from '@/lib/providers';
 | 
				
			||||||
import { PDFLoader } from '@langchain/community/document_loaders/fs/pdf';
 | 
					import { PDFLoader } from '@langchain/community/document_loaders/fs/pdf';
 | 
				
			||||||
import { DocxLoader } from '@langchain/community/document_loaders/fs/docx';
 | 
					import { DocxLoader } from '@langchain/community/document_loaders/fs/docx';
 | 
				
			||||||
import { RecursiveCharacterTextSplitter } from '@langchain/textsplitters';
 | 
					import { RecursiveCharacterTextSplitter } from '@langchain/textsplitters';
 | 
				
			||||||
import { Document } from '@langchain/core/documents';
 | 
					import { Document } from 'langchain/document';
 | 
				
			||||||
import ModelRegistry from '@/lib/models/registry';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface FileRes {
 | 
					interface FileRes {
 | 
				
			||||||
  fileName: string;
 | 
					  fileName: string;
 | 
				
			||||||
@@ -30,8 +30,8 @@ export async function POST(req: Request) {
 | 
				
			|||||||
    const formData = await req.formData();
 | 
					    const formData = await req.formData();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const files = formData.getAll('files') as File[];
 | 
					    const files = formData.getAll('files') as File[];
 | 
				
			||||||
    const embedding_model = formData.get('embedding_model_key') as string;
 | 
					    const embedding_model = formData.get('embedding_model');
 | 
				
			||||||
    const embedding_model_provider = formData.get('embedding_model_provider_id') as string;
 | 
					    const embedding_model_provider = formData.get('embedding_model_provider');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!embedding_model || !embedding_model_provider) {
 | 
					    if (!embedding_model || !embedding_model_provider) {
 | 
				
			||||||
      return NextResponse.json(
 | 
					      return NextResponse.json(
 | 
				
			||||||
@@ -40,9 +40,20 @@ export async function POST(req: Request) {
 | 
				
			|||||||
      );
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const registry = new ModelRegistry();
 | 
					    const embeddingModels = await getAvailableEmbeddingModelProviders();
 | 
				
			||||||
 | 
					    const provider =
 | 
				
			||||||
 | 
					      embedding_model_provider ?? Object.keys(embeddingModels)[0];
 | 
				
			||||||
 | 
					    const embeddingModel =
 | 
				
			||||||
 | 
					      embedding_model ?? Object.keys(embeddingModels[provider as string])[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const model = await registry.loadEmbeddingModel(embedding_model_provider, embedding_model);
 | 
					    let embeddingsModel =
 | 
				
			||||||
 | 
					      embeddingModels[provider as string]?.[embeddingModel as string]?.model;
 | 
				
			||||||
 | 
					    if (!embeddingsModel) {
 | 
				
			||||||
 | 
					      return NextResponse.json(
 | 
				
			||||||
 | 
					        { message: 'Invalid embedding model selected' },
 | 
				
			||||||
 | 
					        { status: 400 },
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const processedFiles: FileRes[] = [];
 | 
					    const processedFiles: FileRes[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -87,7 +98,7 @@ export async function POST(req: Request) {
 | 
				
			|||||||
          }),
 | 
					          }),
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const embeddings = await model.embedDocuments(
 | 
					        const embeddings = await embeddingsModel.embedDocuments(
 | 
				
			||||||
          splitted.map((doc) => doc.pageContent),
 | 
					          splitted.map((doc) => doc.pageContent),
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        const embeddingsDataPath = filePath.replace(
 | 
					        const embeddingsDataPath = filePath.replace(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,12 +1,23 @@
 | 
				
			|||||||
import handleVideoSearch from '@/lib/chains/videoSearchAgent';
 | 
					import handleVideoSearch from '@/lib/chains/videoSearchAgent';
 | 
				
			||||||
import ModelRegistry from '@/lib/models/registry';
 | 
					import {
 | 
				
			||||||
import { ModelWithProvider } from '@/lib/models/types';
 | 
					  getCustomOpenaiApiKey,
 | 
				
			||||||
 | 
					  getCustomOpenaiApiUrl,
 | 
				
			||||||
 | 
					  getCustomOpenaiModelName,
 | 
				
			||||||
 | 
					} from '@/lib/config';
 | 
				
			||||||
 | 
					import { getAvailableChatModelProviders } from '@/lib/providers';
 | 
				
			||||||
 | 
					import { BaseChatModel } from '@langchain/core/language_models/chat_models';
 | 
				
			||||||
import { AIMessage, BaseMessage, HumanMessage } from '@langchain/core/messages';
 | 
					import { AIMessage, BaseMessage, HumanMessage } from '@langchain/core/messages';
 | 
				
			||||||
 | 
					import { ChatOpenAI } from '@langchain/openai';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface ChatModel {
 | 
				
			||||||
 | 
					  provider: string;
 | 
				
			||||||
 | 
					  model: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface VideoSearchBody {
 | 
					interface VideoSearchBody {
 | 
				
			||||||
  query: string;
 | 
					  query: string;
 | 
				
			||||||
  chatHistory: any[];
 | 
					  chatHistory: any[];
 | 
				
			||||||
  chatModel: ModelWithProvider;
 | 
					  chatModel?: ChatModel;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const POST = async (req: Request) => {
 | 
					export const POST = async (req: Request) => {
 | 
				
			||||||
@@ -23,12 +34,35 @@ export const POST = async (req: Request) => {
 | 
				
			|||||||
      })
 | 
					      })
 | 
				
			||||||
      .filter((msg) => msg !== undefined) as BaseMessage[];
 | 
					      .filter((msg) => msg !== undefined) as BaseMessage[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const registry = new ModelRegistry();
 | 
					    const chatModelProviders = await getAvailableChatModelProviders();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const llm = await registry.loadChatModel(
 | 
					    const chatModelProvider =
 | 
				
			||||||
      body.chatModel.providerId,
 | 
					      chatModelProviders[
 | 
				
			||||||
      body.chatModel.key,
 | 
					        body.chatModel?.provider || Object.keys(chatModelProviders)[0]
 | 
				
			||||||
    );
 | 
					      ];
 | 
				
			||||||
 | 
					    const chatModel =
 | 
				
			||||||
 | 
					      chatModelProvider[
 | 
				
			||||||
 | 
					        body.chatModel?.model || Object.keys(chatModelProvider)[0]
 | 
				
			||||||
 | 
					      ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let llm: BaseChatModel | undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (body.chatModel?.provider === 'custom_openai') {
 | 
				
			||||||
 | 
					      llm = new ChatOpenAI({
 | 
				
			||||||
 | 
					        openAIApiKey: getCustomOpenaiApiKey(),
 | 
				
			||||||
 | 
					        modelName: getCustomOpenaiModelName(),
 | 
				
			||||||
 | 
					        temperature: 0.7,
 | 
				
			||||||
 | 
					        configuration: {
 | 
				
			||||||
 | 
					          baseURL: getCustomOpenaiApiUrl(),
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      }) as unknown as BaseChatModel;
 | 
				
			||||||
 | 
					    } else if (chatModelProvider && chatModel) {
 | 
				
			||||||
 | 
					      llm = chatModel.model;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!llm) {
 | 
				
			||||||
 | 
					      return Response.json({ error: 'Invalid chat model' }, { status: 400 });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const videos = await handleVideoSearch(
 | 
					    const videos = await handleVideoSearch(
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,174 +0,0 @@
 | 
				
			|||||||
export const POST = async (req: Request) => {
 | 
					 | 
				
			||||||
  try {
 | 
					 | 
				
			||||||
    const body: {
 | 
					 | 
				
			||||||
      lat: number;
 | 
					 | 
				
			||||||
      lng: number;
 | 
					 | 
				
			||||||
      measureUnit: 'Imperial' | 'Metric';
 | 
					 | 
				
			||||||
    } = await req.json();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!body.lat || !body.lng) {
 | 
					 | 
				
			||||||
      return Response.json(
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          message: 'Invalid request.',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        { status: 400 },
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const res = await fetch(
 | 
					 | 
				
			||||||
      `https://api.open-meteo.com/v1/forecast?latitude=${body.lat}&longitude=${body.lng}¤t=weather_code,temperature_2m,is_day,relative_humidity_2m,wind_speed_10m&timezone=auto${
 | 
					 | 
				
			||||||
        body.measureUnit === 'Metric' ? '' : '&temperature_unit=fahrenheit'
 | 
					 | 
				
			||||||
      }${body.measureUnit === 'Metric' ? '' : '&wind_speed_unit=mph'}`,
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const data = await res.json();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (data.error) {
 | 
					 | 
				
			||||||
      console.error(`Error fetching weather data: ${data.reason}`);
 | 
					 | 
				
			||||||
      return Response.json(
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          message: 'An error has occurred.',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        { status: 500 },
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const weather: {
 | 
					 | 
				
			||||||
      temperature: number;
 | 
					 | 
				
			||||||
      condition: string;
 | 
					 | 
				
			||||||
      humidity: number;
 | 
					 | 
				
			||||||
      windSpeed: number;
 | 
					 | 
				
			||||||
      icon: string;
 | 
					 | 
				
			||||||
      temperatureUnit: 'C' | 'F';
 | 
					 | 
				
			||||||
      windSpeedUnit: 'm/s' | 'mph';
 | 
					 | 
				
			||||||
    } = {
 | 
					 | 
				
			||||||
      temperature: data.current.temperature_2m,
 | 
					 | 
				
			||||||
      condition: '',
 | 
					 | 
				
			||||||
      humidity: data.current.relative_humidity_2m,
 | 
					 | 
				
			||||||
      windSpeed: data.current.wind_speed_10m,
 | 
					 | 
				
			||||||
      icon: '',
 | 
					 | 
				
			||||||
      temperatureUnit: body.measureUnit === 'Metric' ? 'C' : 'F',
 | 
					 | 
				
			||||||
      windSpeedUnit: body.measureUnit === 'Metric' ? 'm/s' : 'mph',
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const code = data.current.weather_code;
 | 
					 | 
				
			||||||
    const isDay = data.current.is_day === 1;
 | 
					 | 
				
			||||||
    const dayOrNight = isDay ? 'day' : 'night';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    switch (code) {
 | 
					 | 
				
			||||||
      case 0:
 | 
					 | 
				
			||||||
        weather.icon = `clear-${dayOrNight}`;
 | 
					 | 
				
			||||||
        weather.condition = 'Clear';
 | 
					 | 
				
			||||||
        break;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      case 1:
 | 
					 | 
				
			||||||
        weather.condition = 'Mainly Clear';
 | 
					 | 
				
			||||||
      case 2:
 | 
					 | 
				
			||||||
        weather.condition = 'Partly Cloudy';
 | 
					 | 
				
			||||||
      case 3:
 | 
					 | 
				
			||||||
        weather.icon = `cloudy-1-${dayOrNight}`;
 | 
					 | 
				
			||||||
        weather.condition = 'Cloudy';
 | 
					 | 
				
			||||||
        break;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      case 45:
 | 
					 | 
				
			||||||
        weather.condition = 'Fog';
 | 
					 | 
				
			||||||
      case 48:
 | 
					 | 
				
			||||||
        weather.icon = `fog-${dayOrNight}`;
 | 
					 | 
				
			||||||
        weather.condition = 'Fog';
 | 
					 | 
				
			||||||
        break;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      case 51:
 | 
					 | 
				
			||||||
        weather.condition = 'Light Drizzle';
 | 
					 | 
				
			||||||
      case 53:
 | 
					 | 
				
			||||||
        weather.condition = 'Moderate Drizzle';
 | 
					 | 
				
			||||||
      case 55:
 | 
					 | 
				
			||||||
        weather.icon = `rainy-1-${dayOrNight}`;
 | 
					 | 
				
			||||||
        weather.condition = 'Dense Drizzle';
 | 
					 | 
				
			||||||
        break;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      case 56:
 | 
					 | 
				
			||||||
        weather.condition = 'Light Freezing Drizzle';
 | 
					 | 
				
			||||||
      case 57:
 | 
					 | 
				
			||||||
        weather.icon = `frost-${dayOrNight}`;
 | 
					 | 
				
			||||||
        weather.condition = 'Dense Freezing Drizzle';
 | 
					 | 
				
			||||||
        break;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      case 61:
 | 
					 | 
				
			||||||
        weather.condition = 'Slight Rain';
 | 
					 | 
				
			||||||
      case 63:
 | 
					 | 
				
			||||||
        weather.condition = 'Moderate Rain';
 | 
					 | 
				
			||||||
      case 65:
 | 
					 | 
				
			||||||
        weather.condition = 'Heavy Rain';
 | 
					 | 
				
			||||||
        weather.icon = `rainy-2-${dayOrNight}`;
 | 
					 | 
				
			||||||
        break;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      case 66:
 | 
					 | 
				
			||||||
        weather.condition = 'Light Freezing Rain';
 | 
					 | 
				
			||||||
      case 67:
 | 
					 | 
				
			||||||
        weather.condition = 'Heavy Freezing Rain';
 | 
					 | 
				
			||||||
        weather.icon = 'rain-and-sleet-mix';
 | 
					 | 
				
			||||||
        break;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      case 71:
 | 
					 | 
				
			||||||
        weather.condition = 'Slight Snow Fall';
 | 
					 | 
				
			||||||
      case 73:
 | 
					 | 
				
			||||||
        weather.condition = 'Moderate Snow Fall';
 | 
					 | 
				
			||||||
      case 75:
 | 
					 | 
				
			||||||
        weather.condition = 'Heavy Snow Fall';
 | 
					 | 
				
			||||||
        weather.icon = `snowy-2-${dayOrNight}`;
 | 
					 | 
				
			||||||
        break;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      case 77:
 | 
					 | 
				
			||||||
        weather.condition = 'Snow';
 | 
					 | 
				
			||||||
        weather.icon = `snowy-1-${dayOrNight}`;
 | 
					 | 
				
			||||||
        break;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      case 80:
 | 
					 | 
				
			||||||
        weather.condition = 'Slight Rain Showers';
 | 
					 | 
				
			||||||
      case 81:
 | 
					 | 
				
			||||||
        weather.condition = 'Moderate Rain Showers';
 | 
					 | 
				
			||||||
      case 82:
 | 
					 | 
				
			||||||
        weather.condition = 'Heavy Rain Showers';
 | 
					 | 
				
			||||||
        weather.icon = `rainy-3-${dayOrNight}`;
 | 
					 | 
				
			||||||
        break;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      case 85:
 | 
					 | 
				
			||||||
        weather.condition = 'Slight Snow Showers';
 | 
					 | 
				
			||||||
      case 86:
 | 
					 | 
				
			||||||
        weather.condition = 'Moderate Snow Showers';
 | 
					 | 
				
			||||||
      case 87:
 | 
					 | 
				
			||||||
        weather.condition = 'Heavy Snow Showers';
 | 
					 | 
				
			||||||
        weather.icon = `snowy-3-${dayOrNight}`;
 | 
					 | 
				
			||||||
        break;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      case 95:
 | 
					 | 
				
			||||||
        weather.condition = 'Thunderstorm';
 | 
					 | 
				
			||||||
        weather.icon = `scattered-thunderstorms-${dayOrNight}`;
 | 
					 | 
				
			||||||
        break;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      case 96:
 | 
					 | 
				
			||||||
        weather.condition = 'Thunderstorm with Slight Hail';
 | 
					 | 
				
			||||||
      case 99:
 | 
					 | 
				
			||||||
        weather.condition = 'Thunderstorm with Heavy Hail';
 | 
					 | 
				
			||||||
        weather.icon = 'severe-thunderstorm';
 | 
					 | 
				
			||||||
        break;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      default:
 | 
					 | 
				
			||||||
        weather.icon = `clear-${dayOrNight}`;
 | 
					 | 
				
			||||||
        weather.condition = 'Clear';
 | 
					 | 
				
			||||||
        break;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return Response.json(weather);
 | 
					 | 
				
			||||||
  } catch (err) {
 | 
					 | 
				
			||||||
    console.error('An error occurred while getting home widgets', err);
 | 
					 | 
				
			||||||
    return Response.json(
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        message: 'An error has occurred.',
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        status: 500,
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
@@ -1,10 +1,9 @@
 | 
				
			|||||||
'use client';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import ChatWindow from '@/components/ChatWindow';
 | 
					import ChatWindow from '@/components/ChatWindow';
 | 
				
			||||||
import React from 'react';
 | 
					import React from 'react';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Page = () => {
 | 
					const Page = ({ params }: { params: Promise<{ chatId: string }> }) => {
 | 
				
			||||||
  return <ChatWindow />;
 | 
					  const { chatId } = React.use(params);
 | 
				
			||||||
 | 
					  return <ChatWindow id={chatId} />;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Page;
 | 
					export default Page;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,268 +1,110 @@
 | 
				
			|||||||
'use client';
 | 
					'use client';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { Globe2Icon } from 'lucide-react';
 | 
					import { Search } from 'lucide-react';
 | 
				
			||||||
import { useEffect, useState } from 'react';
 | 
					import { useEffect, useState } from 'react';
 | 
				
			||||||
 | 
					import Link from 'next/link';
 | 
				
			||||||
import { toast } from 'sonner';
 | 
					import { toast } from 'sonner';
 | 
				
			||||||
import { cn } from '@/lib/utils';
 | 
					 | 
				
			||||||
import SmallNewsCard from '@/components/Discover/SmallNewsCard';
 | 
					 | 
				
			||||||
import MajorNewsCard from '@/components/Discover/MajorNewsCard';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface Discover {
 | 
					interface Discover {
 | 
				
			||||||
  title: string;
 | 
					  title: string;
 | 
				
			||||||
  content: string;
 | 
					  content: string;
 | 
				
			||||||
  url: string;
 | 
					  url: string;
 | 
				
			||||||
  thumbnail: string;
 | 
					  thumbnail: string;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const topics: { key: string; display: string }[] = [
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    display: 'Tech & Science',
 | 
					 | 
				
			||||||
    key: 'tech',
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    display: 'Finance',
 | 
					 | 
				
			||||||
    key: 'finance',
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    display: 'Art & Culture',
 | 
					 | 
				
			||||||
    key: 'art',
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    display: 'Sports',
 | 
					 | 
				
			||||||
    key: 'sports',
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    display: 'Entertainment',
 | 
					 | 
				
			||||||
    key: 'entertainment',
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const Page = () => {
 | 
					const Page = () => {
 | 
				
			||||||
  const [discover, setDiscover] = useState<Discover[] | null>(null);
 | 
					  const [discover, setDiscover] = useState<Discover[] | null>(null);
 | 
				
			||||||
  const [loading, setLoading] = useState(true);
 | 
					  const [loading, setLoading] = useState(true);
 | 
				
			||||||
  const [activeTopic, setActiveTopic] = useState<string>(topics[0].key);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const fetchArticles = async (topic: string) => {
 | 
					 | 
				
			||||||
    setLoading(true);
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      const res = await fetch(`/api/discover?topic=${topic}`, {
 | 
					 | 
				
			||||||
        method: 'GET',
 | 
					 | 
				
			||||||
        headers: {
 | 
					 | 
				
			||||||
          'Content-Type': 'application/json',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const data = await res.json();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if (!res.ok) {
 | 
					 | 
				
			||||||
        throw new Error(data.message);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      data.blogs = data.blogs.filter((blog: Discover) => blog.thumbnail);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      setDiscover(data.blogs);
 | 
					 | 
				
			||||||
    } catch (err: any) {
 | 
					 | 
				
			||||||
      console.error('Error fetching data:', err.message);
 | 
					 | 
				
			||||||
      toast.error('Error fetching data');
 | 
					 | 
				
			||||||
    } finally {
 | 
					 | 
				
			||||||
      setLoading(false);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
    fetchArticles(activeTopic);
 | 
					    const fetchData = async () => {
 | 
				
			||||||
  }, [activeTopic]);
 | 
					      try {
 | 
				
			||||||
 | 
					        const res = await fetch(`/api/discover`, {
 | 
				
			||||||
 | 
					          method: 'GET',
 | 
				
			||||||
 | 
					          headers: {
 | 
				
			||||||
 | 
					            'Content-Type': 'application/json',
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					        const data = await res.json();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!res.ok) {
 | 
				
			||||||
 | 
					          throw new Error(data.message);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        data.blogs = data.blogs.filter((blog: Discover) => blog.thumbnail);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        setDiscover(data.blogs);
 | 
				
			||||||
 | 
					      } catch (err: any) {
 | 
				
			||||||
 | 
					        console.error('Error fetching data:', err.message);
 | 
				
			||||||
 | 
					        toast.error('Error fetching data');
 | 
				
			||||||
 | 
					      } finally {
 | 
				
			||||||
 | 
					        setLoading(false);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fetchData();
 | 
				
			||||||
 | 
					  }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return loading ? (
 | 
				
			||||||
 | 
					    <div className="flex flex-row items-center justify-center min-h-screen">
 | 
				
			||||||
 | 
					      <svg
 | 
				
			||||||
 | 
					        aria-hidden="true"
 | 
				
			||||||
 | 
					        className="w-8 h-8 text-light-200 fill-light-secondary dark:text-[#202020] animate-spin dark:fill-[#ffffff3b]"
 | 
				
			||||||
 | 
					        viewBox="0 0 100 101"
 | 
				
			||||||
 | 
					        fill="none"
 | 
				
			||||||
 | 
					        xmlns="http://www.w3.org/2000/svg"
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        <path
 | 
				
			||||||
 | 
					          d="M100 50.5908C100.003 78.2051 78.1951 100.003 50.5908 100C22.9765 99.9972 0.997224 78.018 1 50.4037C1.00281 22.7993 22.8108 0.997224 50.4251 1C78.0395 1.00281 100.018 22.8108 100 50.4251ZM9.08164 50.594C9.06312 73.3997 27.7909 92.1272 50.5966 92.1457C73.4023 92.1642 92.1298 73.4365 92.1483 50.6308C92.1669 27.8251 73.4392 9.0973 50.6335 9.07878C27.8278 9.06026 9.10003 27.787 9.08164 50.594Z"
 | 
				
			||||||
 | 
					          fill="currentColor"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					        <path
 | 
				
			||||||
 | 
					          d="M93.9676 39.0409C96.393 38.4037 97.8624 35.9116 96.9801 33.5533C95.1945 28.8227 92.871 24.3692 90.0681 20.348C85.6237 14.1775 79.4473 9.36872 72.0454 6.45794C64.6435 3.54717 56.3134 2.65431 48.3133 3.89319C45.869 4.27179 44.3768 6.77534 45.014 9.20079C45.6512 11.6262 48.1343 13.0956 50.5786 12.717C56.5073 11.8281 62.5542 12.5399 68.0406 14.7911C73.527 17.0422 78.2187 20.7487 81.5841 25.4923C83.7976 28.5886 85.4467 32.059 86.4416 35.7474C87.1273 38.1189 89.5423 39.6781 91.9676 39.0409Z"
 | 
				
			||||||
 | 
					          fill="currentFill"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      </svg>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  ) : (
 | 
				
			||||||
    <>
 | 
					    <>
 | 
				
			||||||
      <div>
 | 
					      <div>
 | 
				
			||||||
        <div className="flex flex-col pt-10 border-b border-light-200/20 dark:border-dark-200/20 pb-6 px-2">
 | 
					        <div className="flex flex-col pt-4">
 | 
				
			||||||
          <div className="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-4">
 | 
					          <div className="flex items-center">
 | 
				
			||||||
            <div className="flex items-center justify-center">
 | 
					            <Search />
 | 
				
			||||||
              <Globe2Icon size={45} className="mb-2.5" />
 | 
					            <h1 className="text-3xl font-medium p-2">Discover</h1>
 | 
				
			||||||
              <h1
 | 
					 | 
				
			||||||
                className="text-5xl font-normal p-2"
 | 
					 | 
				
			||||||
                style={{ fontFamily: 'PP Editorial, serif' }}
 | 
					 | 
				
			||||||
              >
 | 
					 | 
				
			||||||
                Discover
 | 
					 | 
				
			||||||
              </h1>
 | 
					 | 
				
			||||||
            </div>
 | 
					 | 
				
			||||||
            <div className="flex flex-row items-center space-x-2 overflow-x-auto">
 | 
					 | 
				
			||||||
              {topics.map((t, i) => (
 | 
					 | 
				
			||||||
                <div
 | 
					 | 
				
			||||||
                  key={i}
 | 
					 | 
				
			||||||
                  className={cn(
 | 
					 | 
				
			||||||
                    'border-[0.1px] rounded-full text-sm px-3 py-1 text-nowrap transition duration-200 cursor-pointer',
 | 
					 | 
				
			||||||
                    activeTopic === t.key
 | 
					 | 
				
			||||||
                      ? 'text-cyan-700 dark:text-cyan-300 bg-cyan-300/20 border-cyan-700/60 dar:bg-cyan-300/30 dark:border-cyan-300/40'
 | 
					 | 
				
			||||||
                      : 'border-black/30 dark:border-white/30 text-black/70 dark:text-white/70 hover:text-black dark:hover:text-white hover:border-black/40 dark:hover:border-white/40 hover:bg-black/5 dark:hover:bg-white/5',
 | 
					 | 
				
			||||||
                  )}
 | 
					 | 
				
			||||||
                  onClick={() => setActiveTopic(t.key)}
 | 
					 | 
				
			||||||
                >
 | 
					 | 
				
			||||||
                  <span>{t.display}</span>
 | 
					 | 
				
			||||||
                </div>
 | 
					 | 
				
			||||||
              ))}
 | 
					 | 
				
			||||||
            </div>
 | 
					 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
 | 
					          <hr className="border-t border-[#2B2C2C] my-4 w-full" />
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        {loading ? (
 | 
					        <div className="grid lg:grid-cols-3 sm:grid-cols-2 grid-cols-1 gap-4 pb-28 lg:pb-8 w-full justify-items-center lg:justify-items-start">
 | 
				
			||||||
          <div className="flex flex-row items-center justify-center min-h-screen">
 | 
					          {discover &&
 | 
				
			||||||
            <svg
 | 
					            discover?.map((item, i) => (
 | 
				
			||||||
              aria-hidden="true"
 | 
					              <Link
 | 
				
			||||||
              className="w-8 h-8 text-light-200 fill-light-secondary dark:text-[#202020] animate-spin dark:fill-[#ffffff3b]"
 | 
					                href={`/?q=Summary: ${item.url}`}
 | 
				
			||||||
              viewBox="0 0 100 101"
 | 
					                key={i}
 | 
				
			||||||
              fill="none"
 | 
					                className="max-w-sm rounded-lg overflow-hidden bg-light-secondary dark:bg-dark-secondary hover:-translate-y-[1px] transition duration-200"
 | 
				
			||||||
              xmlns="http://www.w3.org/2000/svg"
 | 
					                target="_blank"
 | 
				
			||||||
            >
 | 
					              >
 | 
				
			||||||
              <path
 | 
					                <img
 | 
				
			||||||
                d="M100 50.5908C100.003 78.2051 78.1951 100.003 50.5908 100C22.9765 99.9972 0.997224 78.018 1 50.4037C1.00281 22.7993 22.8108 0.997224 50.4251 1C78.0395 1.00281 100.018 22.8108 100 50.4251ZM9.08164 50.594C9.06312 73.3997 27.7909 92.1272 50.5966 92.1457C73.4023 92.1642 92.1298 73.4365 92.1483 50.6308C92.1669 27.8251 73.4392 9.0973 50.6335 9.07878C27.8278 9.06026 9.10003 27.787 9.08164 50.594Z"
 | 
					                  className="object-cover w-full aspect-video"
 | 
				
			||||||
                fill="currentColor"
 | 
					                  src={
 | 
				
			||||||
              />
 | 
					                    new URL(item.thumbnail).origin +
 | 
				
			||||||
              <path
 | 
					                    new URL(item.thumbnail).pathname +
 | 
				
			||||||
                d="M93.9676 39.0409C96.393 38.4037 97.8624 35.9116 96.9801 33.5533C95.1945 28.8227 92.871 24.3692 90.0681 20.348C85.6237 14.1775 79.4473 9.36872 72.0454 6.45794C64.6435 3.54717 56.3134 2.65431 48.3133 3.89319C45.869 4.27179 44.3768 6.77534 45.014 9.20079C45.6512 11.6262 48.1343 13.0956 50.5786 12.717C56.5073 11.8281 62.5542 12.5399 68.0406 14.7911C73.527 17.0422 78.2187 20.7487 81.5841 25.4923C83.7976 28.5886 85.4467 32.059 86.4416 35.7474C87.1273 38.1189 89.5423 39.6781 91.9676 39.0409Z"
 | 
					                    `?id=${new URL(item.thumbnail).searchParams.get('id')}`
 | 
				
			||||||
                fill="currentFill"
 | 
					 | 
				
			||||||
              />
 | 
					 | 
				
			||||||
            </svg>
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
        ) : (
 | 
					 | 
				
			||||||
          <div className="flex flex-col gap-4 pb-28 pt-5 lg:pb-8 w-full">
 | 
					 | 
				
			||||||
            <div className="block lg:hidden">
 | 
					 | 
				
			||||||
              <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
 | 
					 | 
				
			||||||
                {discover?.map((item, i) => (
 | 
					 | 
				
			||||||
                  <SmallNewsCard key={`mobile-${i}`} item={item} />
 | 
					 | 
				
			||||||
                ))}
 | 
					 | 
				
			||||||
              </div>
 | 
					 | 
				
			||||||
            </div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <div className="hidden lg:block">
 | 
					 | 
				
			||||||
              {discover &&
 | 
					 | 
				
			||||||
                discover.length > 0 &&
 | 
					 | 
				
			||||||
                (() => {
 | 
					 | 
				
			||||||
                  const sections = [];
 | 
					 | 
				
			||||||
                  let index = 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                  while (index < discover.length) {
 | 
					 | 
				
			||||||
                    if (sections.length > 0) {
 | 
					 | 
				
			||||||
                      sections.push(
 | 
					 | 
				
			||||||
                        <hr
 | 
					 | 
				
			||||||
                          key={`sep-${index}`}
 | 
					 | 
				
			||||||
                          className="border-t border-light-200/20 dark:border-dark-200/20 my-3 w-full"
 | 
					 | 
				
			||||||
                        />,
 | 
					 | 
				
			||||||
                      );
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    if (index < discover.length) {
 | 
					 | 
				
			||||||
                      sections.push(
 | 
					 | 
				
			||||||
                        <MajorNewsCard
 | 
					 | 
				
			||||||
                          key={`major-${index}`}
 | 
					 | 
				
			||||||
                          item={discover[index]}
 | 
					 | 
				
			||||||
                          isLeft={false}
 | 
					 | 
				
			||||||
                        />,
 | 
					 | 
				
			||||||
                      );
 | 
					 | 
				
			||||||
                      index++;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    if (index < discover.length) {
 | 
					 | 
				
			||||||
                      sections.push(
 | 
					 | 
				
			||||||
                        <hr
 | 
					 | 
				
			||||||
                          key={`sep-${index}-after`}
 | 
					 | 
				
			||||||
                          className="border-t border-light-200/20 dark:border-dark-200/20 my-3 w-full"
 | 
					 | 
				
			||||||
                        />,
 | 
					 | 
				
			||||||
                      );
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    if (index < discover.length) {
 | 
					 | 
				
			||||||
                      const smallCards = discover.slice(index, index + 3);
 | 
					 | 
				
			||||||
                      sections.push(
 | 
					 | 
				
			||||||
                        <div
 | 
					 | 
				
			||||||
                          key={`small-group-${index}`}
 | 
					 | 
				
			||||||
                          className="grid lg:grid-cols-3 sm:grid-cols-2 grid-cols-1 gap-4"
 | 
					 | 
				
			||||||
                        >
 | 
					 | 
				
			||||||
                          {smallCards.map((item, i) => (
 | 
					 | 
				
			||||||
                            <SmallNewsCard
 | 
					 | 
				
			||||||
                              key={`small-${index + i}`}
 | 
					 | 
				
			||||||
                              item={item}
 | 
					 | 
				
			||||||
                            />
 | 
					 | 
				
			||||||
                          ))}
 | 
					 | 
				
			||||||
                        </div>,
 | 
					 | 
				
			||||||
                      );
 | 
					 | 
				
			||||||
                      index += 3;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    if (index < discover.length) {
 | 
					 | 
				
			||||||
                      sections.push(
 | 
					 | 
				
			||||||
                        <hr
 | 
					 | 
				
			||||||
                          key={`sep-${index}-after-small`}
 | 
					 | 
				
			||||||
                          className="border-t border-light-200/20 dark:border-dark-200/20 my-3 w-full"
 | 
					 | 
				
			||||||
                        />,
 | 
					 | 
				
			||||||
                      );
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    if (index < discover.length - 1) {
 | 
					 | 
				
			||||||
                      const twoMajorCards = discover.slice(index, index + 2);
 | 
					 | 
				
			||||||
                      twoMajorCards.forEach((item, i) => {
 | 
					 | 
				
			||||||
                        sections.push(
 | 
					 | 
				
			||||||
                          <MajorNewsCard
 | 
					 | 
				
			||||||
                            key={`double-${index + i}`}
 | 
					 | 
				
			||||||
                            item={item}
 | 
					 | 
				
			||||||
                            isLeft={i === 0}
 | 
					 | 
				
			||||||
                          />,
 | 
					 | 
				
			||||||
                        );
 | 
					 | 
				
			||||||
                        if (i === 0) {
 | 
					 | 
				
			||||||
                          sections.push(
 | 
					 | 
				
			||||||
                            <hr
 | 
					 | 
				
			||||||
                              key={`sep-double-${index + i}`}
 | 
					 | 
				
			||||||
                              className="border-t border-light-200/20 dark:border-dark-200/20 my-3 w-full"
 | 
					 | 
				
			||||||
                            />,
 | 
					 | 
				
			||||||
                          );
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                      });
 | 
					 | 
				
			||||||
                      index += 2;
 | 
					 | 
				
			||||||
                    } else if (index < discover.length) {
 | 
					 | 
				
			||||||
                      sections.push(
 | 
					 | 
				
			||||||
                        <MajorNewsCard
 | 
					 | 
				
			||||||
                          key={`final-major-${index}`}
 | 
					 | 
				
			||||||
                          item={discover[index]}
 | 
					 | 
				
			||||||
                          isLeft={true}
 | 
					 | 
				
			||||||
                        />,
 | 
					 | 
				
			||||||
                      );
 | 
					 | 
				
			||||||
                      index++;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    if (index < discover.length) {
 | 
					 | 
				
			||||||
                      sections.push(
 | 
					 | 
				
			||||||
                        <hr
 | 
					 | 
				
			||||||
                          key={`sep-${index}-after-major`}
 | 
					 | 
				
			||||||
                          className="border-t border-light-200/20 dark:border-dark-200/20 my-3 w-full"
 | 
					 | 
				
			||||||
                        />,
 | 
					 | 
				
			||||||
                      );
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    if (index < discover.length) {
 | 
					 | 
				
			||||||
                      const smallCards = discover.slice(index, index + 3);
 | 
					 | 
				
			||||||
                      sections.push(
 | 
					 | 
				
			||||||
                        <div
 | 
					 | 
				
			||||||
                          key={`small-group-2-${index}`}
 | 
					 | 
				
			||||||
                          className="grid lg:grid-cols-3 sm:grid-cols-2 grid-cols-1 gap-4"
 | 
					 | 
				
			||||||
                        >
 | 
					 | 
				
			||||||
                          {smallCards.map((item, i) => (
 | 
					 | 
				
			||||||
                            <SmallNewsCard
 | 
					 | 
				
			||||||
                              key={`small-2-${index + i}`}
 | 
					 | 
				
			||||||
                              item={item}
 | 
					 | 
				
			||||||
                            />
 | 
					 | 
				
			||||||
                          ))}
 | 
					 | 
				
			||||||
                        </div>,
 | 
					 | 
				
			||||||
                      );
 | 
					 | 
				
			||||||
                      index += 3;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                  }
 | 
					                  }
 | 
				
			||||||
 | 
					                  alt={item.title}
 | 
				
			||||||
                  return sections;
 | 
					                />
 | 
				
			||||||
                })()}
 | 
					                <div className="px-6 py-4">
 | 
				
			||||||
            </div>
 | 
					                  <div className="font-bold text-lg mb-2">
 | 
				
			||||||
          </div>
 | 
					                    {item.title.slice(0, 100)}...
 | 
				
			||||||
        )}
 | 
					                  </div>
 | 
				
			||||||
 | 
					                  <p className="text-black-70 dark:text-white/70 text-sm">
 | 
				
			||||||
 | 
					                    {item.content.slice(0, 100)}...
 | 
				
			||||||
 | 
					                  </p>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					              </Link>
 | 
				
			||||||
 | 
					            ))}
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </>
 | 
					    </>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,14 +2,6 @@
 | 
				
			|||||||
@tailwind components;
 | 
					@tailwind components;
 | 
				
			||||||
@tailwind utilities;
 | 
					@tailwind utilities;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@font-face {
 | 
					 | 
				
			||||||
  font-family: 'PP Editorial';
 | 
					 | 
				
			||||||
  src: url('/fonts/pp-ed-ul.otf') format('opentype');
 | 
					 | 
				
			||||||
  font-weight: 300;
 | 
					 | 
				
			||||||
  font-style: normal;
 | 
					 | 
				
			||||||
  font-display: swap;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@layer base {
 | 
					@layer base {
 | 
				
			||||||
  .overflow-hidden-scrollable {
 | 
					  .overflow-hidden-scrollable {
 | 
				
			||||||
    -ms-overflow-style: none;
 | 
					    -ms-overflow-style: none;
 | 
				
			||||||
@@ -18,82 +10,4 @@
 | 
				
			|||||||
  .overflow-hidden-scrollable::-webkit-scrollbar {
 | 
					  .overflow-hidden-scrollable::-webkit-scrollbar {
 | 
				
			||||||
    display: none;
 | 
					    display: none;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					 | 
				
			||||||
  * {
 | 
					 | 
				
			||||||
    scrollbar-width: thin;
 | 
					 | 
				
			||||||
    scrollbar-color: #e8edf1 transparent; /* light-200 */
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  *::-webkit-scrollbar {
 | 
					 | 
				
			||||||
    width: 6px;
 | 
					 | 
				
			||||||
    height: 6px;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  *::-webkit-scrollbar-track {
 | 
					 | 
				
			||||||
    background: transparent;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  *::-webkit-scrollbar-thumb {
 | 
					 | 
				
			||||||
    background: #e8edf1; /* light-200 */
 | 
					 | 
				
			||||||
    border-radius: 3px;
 | 
					 | 
				
			||||||
    transition: background 0.2s ease;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  *::-webkit-scrollbar-thumb:hover {
 | 
					 | 
				
			||||||
    background: #d0d7de; /* light-300 */
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @media (prefers-color-scheme: dark) {
 | 
					 | 
				
			||||||
    * {
 | 
					 | 
				
			||||||
      scrollbar-color: #21262d transparent; /* dark-200 */
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    *::-webkit-scrollbar-thumb {
 | 
					 | 
				
			||||||
      background: #21262d; /* dark-200 */
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    *::-webkit-scrollbar-thumb:hover {
 | 
					 | 
				
			||||||
      background: #30363d; /* dark-300 */
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  :root.dark *,
 | 
					 | 
				
			||||||
  html.dark *,
 | 
					 | 
				
			||||||
  body.dark * {
 | 
					 | 
				
			||||||
    scrollbar-color: #21262d transparent; /* dark-200 */
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  :root.dark *::-webkit-scrollbar-thumb,
 | 
					 | 
				
			||||||
  html.dark *::-webkit-scrollbar-thumb,
 | 
					 | 
				
			||||||
  body.dark *::-webkit-scrollbar-thumb {
 | 
					 | 
				
			||||||
    background: #21262d; /* dark-200 */
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  :root.dark *::-webkit-scrollbar-thumb:hover,
 | 
					 | 
				
			||||||
  html.dark *::-webkit-scrollbar-thumb:hover,
 | 
					 | 
				
			||||||
  body.dark *::-webkit-scrollbar-thumb:hover {
 | 
					 | 
				
			||||||
    background: #30363d; /* dark-300 */
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  html {
 | 
					 | 
				
			||||||
    scroll-behavior: smooth;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@layer utilities {
 | 
					 | 
				
			||||||
  .line-clamp-2 {
 | 
					 | 
				
			||||||
    display: -webkit-box;
 | 
					 | 
				
			||||||
    -webkit-box-orient: vertical;
 | 
					 | 
				
			||||||
    -webkit-line-clamp: 2;
 | 
					 | 
				
			||||||
    line-clamp: 2;
 | 
					 | 
				
			||||||
    overflow: hidden;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@media screen and (-webkit-min-device-pixel-ratio: 0) {
 | 
					 | 
				
			||||||
  select,
 | 
					 | 
				
			||||||
  textarea,
 | 
					 | 
				
			||||||
  input {
 | 
					 | 
				
			||||||
    font-size: 16px !important;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,3 @@
 | 
				
			|||||||
export const dynamic = 'force-dynamic';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import type { Metadata } from 'next';
 | 
					import type { Metadata } from 'next';
 | 
				
			||||||
import { Montserrat } from 'next/font/google';
 | 
					import { Montserrat } from 'next/font/google';
 | 
				
			||||||
import './globals.css';
 | 
					import './globals.css';
 | 
				
			||||||
@@ -7,9 +5,6 @@ import { cn } from '@/lib/utils';
 | 
				
			|||||||
import Sidebar from '@/components/Sidebar';
 | 
					import Sidebar from '@/components/Sidebar';
 | 
				
			||||||
import { Toaster } from 'sonner';
 | 
					import { Toaster } from 'sonner';
 | 
				
			||||||
import ThemeProvider from '@/components/theme/Provider';
 | 
					import ThemeProvider from '@/components/theme/Provider';
 | 
				
			||||||
import configManager from '@/lib/config';
 | 
					 | 
				
			||||||
import SetupWizard from '@/components/Setup/SetupWizard';
 | 
					 | 
				
			||||||
import { ChatProvider } from '@/lib/hooks/useChat';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const montserrat = Montserrat({
 | 
					const montserrat = Montserrat({
 | 
				
			||||||
  weight: ['300', '400', '500', '700'],
 | 
					  weight: ['300', '400', '500', '700'],
 | 
				
			||||||
@@ -29,29 +24,20 @@ export default function RootLayout({
 | 
				
			|||||||
}: Readonly<{
 | 
					}: Readonly<{
 | 
				
			||||||
  children: React.ReactNode;
 | 
					  children: React.ReactNode;
 | 
				
			||||||
}>) {
 | 
					}>) {
 | 
				
			||||||
  const setupComplete = configManager.isSetupComplete();
 | 
					 | 
				
			||||||
  const configSections = configManager.getUIConfigSections();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <html className="h-full" lang="en" suppressHydrationWarning>
 | 
					    <html className="h-full" lang="en" suppressHydrationWarning>
 | 
				
			||||||
      <body className={cn('h-full', montserrat.className)}>
 | 
					      <body className={cn('h-full', montserrat.className)}>
 | 
				
			||||||
        <ThemeProvider>
 | 
					        <ThemeProvider>
 | 
				
			||||||
          {setupComplete ? (
 | 
					          <Sidebar>{children}</Sidebar>
 | 
				
			||||||
            <ChatProvider>
 | 
					          <Toaster
 | 
				
			||||||
              <Sidebar>{children}</Sidebar>
 | 
					            toastOptions={{
 | 
				
			||||||
              <Toaster
 | 
					              unstyled: true,
 | 
				
			||||||
                toastOptions={{
 | 
					              classNames: {
 | 
				
			||||||
                  unstyled: true,
 | 
					                toast:
 | 
				
			||||||
                  classNames: {
 | 
					                  'bg-light-primary dark:bg-dark-secondary dark:text-white/70 text-black-70 rounded-lg p-4 flex flex-row items-center space-x-2',
 | 
				
			||||||
                    toast:
 | 
					              },
 | 
				
			||||||
                      'bg-light-secondary dark:bg-dark-secondary dark:text-white/70 text-black-70 rounded-lg p-4 flex flex-row items-center space-x-2',
 | 
					            }}
 | 
				
			||||||
                  },
 | 
					          />
 | 
				
			||||||
                }}
 | 
					 | 
				
			||||||
              />
 | 
					 | 
				
			||||||
            </ChatProvider>
 | 
					 | 
				
			||||||
          ) : (
 | 
					 | 
				
			||||||
            <SetupWizard configSections={configSections} />
 | 
					 | 
				
			||||||
          )}
 | 
					 | 
				
			||||||
        </ThemeProvider>
 | 
					        </ThemeProvider>
 | 
				
			||||||
      </body>
 | 
					      </body>
 | 
				
			||||||
    </html>
 | 
					    </html>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,54 +0,0 @@
 | 
				
			|||||||
import type { MetadataRoute } from 'next';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default function manifest(): MetadataRoute.Manifest {
 | 
					 | 
				
			||||||
  return {
 | 
					 | 
				
			||||||
    name: 'Perplexica - Chat with the internet',
 | 
					 | 
				
			||||||
    short_name: 'Perplexica',
 | 
					 | 
				
			||||||
    description:
 | 
					 | 
				
			||||||
      'Perplexica is an AI powered chatbot that is connected to the internet.',
 | 
					 | 
				
			||||||
    start_url: '/',
 | 
					 | 
				
			||||||
    display: 'standalone',
 | 
					 | 
				
			||||||
    background_color: '#0a0a0a',
 | 
					 | 
				
			||||||
    theme_color: '#0a0a0a',
 | 
					 | 
				
			||||||
    screenshots: [
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        src: '/screenshots/p1.png',
 | 
					 | 
				
			||||||
        form_factor: 'wide',
 | 
					 | 
				
			||||||
        sizes: '2560x1600',
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        src: '/screenshots/p2.png',
 | 
					 | 
				
			||||||
        form_factor: 'wide',
 | 
					 | 
				
			||||||
        sizes: '2560x1600',
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        src: '/screenshots/p1_small.png',
 | 
					 | 
				
			||||||
        form_factor: 'narrow',
 | 
					 | 
				
			||||||
        sizes: '828x1792',
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        src: '/screenshots/p2_small.png',
 | 
					 | 
				
			||||||
        form_factor: 'narrow',
 | 
					 | 
				
			||||||
        sizes: '828x1792',
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    ],
 | 
					 | 
				
			||||||
    icons: [
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        src: '/icon-50.png',
 | 
					 | 
				
			||||||
        sizes: '50x50',
 | 
					 | 
				
			||||||
        type: 'image/png' as const,
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        src: '/icon-100.png',
 | 
					 | 
				
			||||||
        sizes: '100x100',
 | 
					 | 
				
			||||||
        type: 'image/png',
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        src: '/icon.png',
 | 
					 | 
				
			||||||
        sizes: '440x440',
 | 
					 | 
				
			||||||
        type: 'image/png',
 | 
					 | 
				
			||||||
        purpose: 'any',
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    ],
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
import ChatWindow from '@/components/ChatWindow';
 | 
					import ChatWindow from '@/components/ChatWindow';
 | 
				
			||||||
import { Metadata } from 'next';
 | 
					import { Metadata } from 'next';
 | 
				
			||||||
 | 
					import { Suspense } from 'react';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const metadata: Metadata = {
 | 
					export const metadata: Metadata = {
 | 
				
			||||||
  title: 'Chat - Perplexica',
 | 
					  title: 'Chat - Perplexica',
 | 
				
			||||||
@@ -7,7 +8,13 @@ export const metadata: Metadata = {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Home = () => {
 | 
					const Home = () => {
 | 
				
			||||||
  return <ChatWindow />;
 | 
					  return (
 | 
				
			||||||
 | 
					    <div>
 | 
				
			||||||
 | 
					      <Suspense>
 | 
				
			||||||
 | 
					        <ChatWindow />
 | 
				
			||||||
 | 
					      </Suspense>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Home;
 | 
					export default Home;
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										870
									
								
								src/app/settings/page.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,870 @@
 | 
				
			|||||||
 | 
					'use client';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Settings as SettingsIcon, ArrowLeft, Loader2 } from 'lucide-react';
 | 
				
			||||||
 | 
					import { useEffect, useState } from 'react';
 | 
				
			||||||
 | 
					import { cn } from '@/lib/utils';
 | 
				
			||||||
 | 
					import { Switch } from '@headlessui/react';
 | 
				
			||||||
 | 
					import ThemeSwitcher from '@/components/theme/Switcher';
 | 
				
			||||||
 | 
					import { ImagesIcon, VideoIcon } from 'lucide-react';
 | 
				
			||||||
 | 
					import Link from 'next/link';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface SettingsType {
 | 
				
			||||||
 | 
					  chatModelProviders: {
 | 
				
			||||||
 | 
					    [key: string]: [Record<string, any>];
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					  embeddingModelProviders: {
 | 
				
			||||||
 | 
					    [key: string]: [Record<string, any>];
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					  openaiApiKey: string;
 | 
				
			||||||
 | 
					  groqApiKey: string;
 | 
				
			||||||
 | 
					  anthropicApiKey: string;
 | 
				
			||||||
 | 
					  geminiApiKey: string;
 | 
				
			||||||
 | 
					  ollamaApiUrl: string;
 | 
				
			||||||
 | 
					  deepseekApiKey: string;
 | 
				
			||||||
 | 
					  customOpenaiApiKey: string;
 | 
				
			||||||
 | 
					  customOpenaiApiUrl: string;
 | 
				
			||||||
 | 
					  customOpenaiModelName: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
 | 
				
			||||||
 | 
					  isSaving?: boolean;
 | 
				
			||||||
 | 
					  onSave?: (value: string) => void;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Input = ({ className, isSaving, onSave, ...restProps }: InputProps) => {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div className="relative">
 | 
				
			||||||
 | 
					      <input
 | 
				
			||||||
 | 
					        {...restProps}
 | 
				
			||||||
 | 
					        className={cn(
 | 
				
			||||||
 | 
					          'bg-light-secondary dark:bg-dark-secondary w-full px-3 py-2 flex items-center overflow-hidden border border-light-200 dark:border-dark-200 dark:text-white rounded-lg text-sm',
 | 
				
			||||||
 | 
					          isSaving && 'pr-10',
 | 
				
			||||||
 | 
					          className,
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
 | 
					        onBlur={(e) => onSave?.(e.target.value)}
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					      {isSaving && (
 | 
				
			||||||
 | 
					        <div className="absolute right-3 top-1/2 -translate-y-1/2">
 | 
				
			||||||
 | 
					          <Loader2
 | 
				
			||||||
 | 
					            size={16}
 | 
				
			||||||
 | 
					            className="animate-spin text-black/70 dark:text-white/70"
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface TextareaProps extends React.InputHTMLAttributes<HTMLTextAreaElement> {
 | 
				
			||||||
 | 
					  isSaving?: boolean;
 | 
				
			||||||
 | 
					  onSave?: (value: string) => void;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Textarea = ({
 | 
				
			||||||
 | 
					  className,
 | 
				
			||||||
 | 
					  isSaving,
 | 
				
			||||||
 | 
					  onSave,
 | 
				
			||||||
 | 
					  ...restProps
 | 
				
			||||||
 | 
					}: TextareaProps) => {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div className="relative">
 | 
				
			||||||
 | 
					      <textarea
 | 
				
			||||||
 | 
					        placeholder="Any special instructions for the LLM"
 | 
				
			||||||
 | 
					        className="placeholder:text-sm text-sm w-full flex items-center justify-between p-3 bg-light-secondary dark:bg-dark-secondary rounded-lg hover:bg-light-200 dark:hover:bg-dark-200 transition-colors"
 | 
				
			||||||
 | 
					        rows={4}
 | 
				
			||||||
 | 
					        onBlur={(e) => onSave?.(e.target.value)}
 | 
				
			||||||
 | 
					        {...restProps}
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					      {isSaving && (
 | 
				
			||||||
 | 
					        <div className="absolute right-3 top-3">
 | 
				
			||||||
 | 
					          <Loader2
 | 
				
			||||||
 | 
					            size={16}
 | 
				
			||||||
 | 
					            className="animate-spin text-black/70 dark:text-white/70"
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Select = ({
 | 
				
			||||||
 | 
					  className,
 | 
				
			||||||
 | 
					  options,
 | 
				
			||||||
 | 
					  ...restProps
 | 
				
			||||||
 | 
					}: React.SelectHTMLAttributes<HTMLSelectElement> & {
 | 
				
			||||||
 | 
					  options: { value: string; label: string; disabled?: boolean }[];
 | 
				
			||||||
 | 
					}) => {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <select
 | 
				
			||||||
 | 
					      {...restProps}
 | 
				
			||||||
 | 
					      className={cn(
 | 
				
			||||||
 | 
					        'bg-light-secondary dark:bg-dark-secondary px-3 py-2 flex items-center overflow-hidden border border-light-200 dark:border-dark-200 dark:text-white rounded-lg text-sm',
 | 
				
			||||||
 | 
					        className,
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					    >
 | 
				
			||||||
 | 
					      {options.map(({ label, value, disabled }) => (
 | 
				
			||||||
 | 
					        <option key={value} value={value} disabled={disabled}>
 | 
				
			||||||
 | 
					          {label}
 | 
				
			||||||
 | 
					        </option>
 | 
				
			||||||
 | 
					      ))}
 | 
				
			||||||
 | 
					    </select>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const SettingsSection = ({
 | 
				
			||||||
 | 
					  title,
 | 
				
			||||||
 | 
					  children,
 | 
				
			||||||
 | 
					}: {
 | 
				
			||||||
 | 
					  title: string;
 | 
				
			||||||
 | 
					  children: React.ReactNode;
 | 
				
			||||||
 | 
					}) => (
 | 
				
			||||||
 | 
					  <div className="flex flex-col space-y-4 p-4 bg-light-secondary/50 dark:bg-dark-secondary/50 rounded-xl border border-light-200 dark:border-dark-200">
 | 
				
			||||||
 | 
					    <h2 className="text-black/90 dark:text-white/90 font-medium">{title}</h2>
 | 
				
			||||||
 | 
					    {children}
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Page = () => {
 | 
				
			||||||
 | 
					  const [config, setConfig] = useState<SettingsType | null>(null);
 | 
				
			||||||
 | 
					  const [chatModels, setChatModels] = useState<Record<string, any>>({});
 | 
				
			||||||
 | 
					  const [embeddingModels, setEmbeddingModels] = useState<Record<string, any>>(
 | 
				
			||||||
 | 
					    {},
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  const [selectedChatModelProvider, setSelectedChatModelProvider] = useState<
 | 
				
			||||||
 | 
					    string | null
 | 
				
			||||||
 | 
					  >(null);
 | 
				
			||||||
 | 
					  const [selectedChatModel, setSelectedChatModel] = useState<string | null>(
 | 
				
			||||||
 | 
					    null,
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  const [selectedEmbeddingModelProvider, setSelectedEmbeddingModelProvider] =
 | 
				
			||||||
 | 
					    useState<string | null>(null);
 | 
				
			||||||
 | 
					  const [selectedEmbeddingModel, setSelectedEmbeddingModel] = useState<
 | 
				
			||||||
 | 
					    string | null
 | 
				
			||||||
 | 
					  >(null);
 | 
				
			||||||
 | 
					  const [isLoading, setIsLoading] = useState(false);
 | 
				
			||||||
 | 
					  const [automaticImageSearch, setAutomaticImageSearch] = useState(false);
 | 
				
			||||||
 | 
					  const [automaticVideoSearch, setAutomaticVideoSearch] = useState(false);
 | 
				
			||||||
 | 
					  const [systemInstructions, setSystemInstructions] = useState<string>('');
 | 
				
			||||||
 | 
					  const [savingStates, setSavingStates] = useState<Record<string, boolean>>({});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    const fetchConfig = async () => {
 | 
				
			||||||
 | 
					      setIsLoading(true);
 | 
				
			||||||
 | 
					      const res = await fetch(`/api/config`, {
 | 
				
			||||||
 | 
					        headers: {
 | 
				
			||||||
 | 
					          'Content-Type': 'application/json',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const data = (await res.json()) as SettingsType;
 | 
				
			||||||
 | 
					      setConfig(data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const chatModelProvidersKeys = Object.keys(data.chatModelProviders || {});
 | 
				
			||||||
 | 
					      const embeddingModelProvidersKeys = Object.keys(
 | 
				
			||||||
 | 
					        data.embeddingModelProviders || {},
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const defaultChatModelProvider =
 | 
				
			||||||
 | 
					        chatModelProvidersKeys.length > 0 ? chatModelProvidersKeys[0] : '';
 | 
				
			||||||
 | 
					      const defaultEmbeddingModelProvider =
 | 
				
			||||||
 | 
					        embeddingModelProvidersKeys.length > 0
 | 
				
			||||||
 | 
					          ? embeddingModelProvidersKeys[0]
 | 
				
			||||||
 | 
					          : '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const chatModelProvider =
 | 
				
			||||||
 | 
					        localStorage.getItem('chatModelProvider') ||
 | 
				
			||||||
 | 
					        defaultChatModelProvider ||
 | 
				
			||||||
 | 
					        '';
 | 
				
			||||||
 | 
					      const chatModel =
 | 
				
			||||||
 | 
					        localStorage.getItem('chatModel') ||
 | 
				
			||||||
 | 
					        (data.chatModelProviders &&
 | 
				
			||||||
 | 
					        data.chatModelProviders[chatModelProvider]?.length > 0
 | 
				
			||||||
 | 
					          ? data.chatModelProviders[chatModelProvider][0].name
 | 
				
			||||||
 | 
					          : undefined) ||
 | 
				
			||||||
 | 
					        '';
 | 
				
			||||||
 | 
					      const embeddingModelProvider =
 | 
				
			||||||
 | 
					        localStorage.getItem('embeddingModelProvider') ||
 | 
				
			||||||
 | 
					        defaultEmbeddingModelProvider ||
 | 
				
			||||||
 | 
					        '';
 | 
				
			||||||
 | 
					      const embeddingModel =
 | 
				
			||||||
 | 
					        localStorage.getItem('embeddingModel') ||
 | 
				
			||||||
 | 
					        (data.embeddingModelProviders &&
 | 
				
			||||||
 | 
					          data.embeddingModelProviders[embeddingModelProvider]?.[0].name) ||
 | 
				
			||||||
 | 
					        '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      setSelectedChatModelProvider(chatModelProvider);
 | 
				
			||||||
 | 
					      setSelectedChatModel(chatModel);
 | 
				
			||||||
 | 
					      setSelectedEmbeddingModelProvider(embeddingModelProvider);
 | 
				
			||||||
 | 
					      setSelectedEmbeddingModel(embeddingModel);
 | 
				
			||||||
 | 
					      setChatModels(data.chatModelProviders || {});
 | 
				
			||||||
 | 
					      setEmbeddingModels(data.embeddingModelProviders || {});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      setAutomaticImageSearch(
 | 
				
			||||||
 | 
					        localStorage.getItem('autoImageSearch') === 'true',
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      setAutomaticVideoSearch(
 | 
				
			||||||
 | 
					        localStorage.getItem('autoVideoSearch') === 'true',
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      setSystemInstructions(localStorage.getItem('systemInstructions')!);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      setIsLoading(false);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fetchConfig();
 | 
				
			||||||
 | 
					  }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const saveConfig = async (key: string, value: any) => {
 | 
				
			||||||
 | 
					    setSavingStates((prev) => ({ ...prev, [key]: true }));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const updatedConfig = {
 | 
				
			||||||
 | 
					        ...config,
 | 
				
			||||||
 | 
					        [key]: value,
 | 
				
			||||||
 | 
					      } as SettingsType;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const response = await fetch(`/api/config`, {
 | 
				
			||||||
 | 
					        method: 'POST',
 | 
				
			||||||
 | 
					        headers: {
 | 
				
			||||||
 | 
					          'Content-Type': 'application/json',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        body: JSON.stringify(updatedConfig),
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (!response.ok) {
 | 
				
			||||||
 | 
					        throw new Error('Failed to update config');
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      setConfig(updatedConfig);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (
 | 
				
			||||||
 | 
					        key.toLowerCase().includes('api') ||
 | 
				
			||||||
 | 
					        key.toLowerCase().includes('url')
 | 
				
			||||||
 | 
					      ) {
 | 
				
			||||||
 | 
					        const res = await fetch(`/api/config`, {
 | 
				
			||||||
 | 
					          headers: {
 | 
				
			||||||
 | 
					            'Content-Type': 'application/json',
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!res.ok) {
 | 
				
			||||||
 | 
					          throw new Error('Failed to fetch updated config');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const data = await res.json();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        setChatModels(data.chatModelProviders || {});
 | 
				
			||||||
 | 
					        setEmbeddingModels(data.embeddingModelProviders || {});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const currentChatProvider = selectedChatModelProvider;
 | 
				
			||||||
 | 
					        const newChatProviders = Object.keys(data.chatModelProviders || {});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!currentChatProvider && newChatProviders.length > 0) {
 | 
				
			||||||
 | 
					          const firstProvider = newChatProviders[0];
 | 
				
			||||||
 | 
					          const firstModel = data.chatModelProviders[firstProvider]?.[0]?.name;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          if (firstModel) {
 | 
				
			||||||
 | 
					            setSelectedChatModelProvider(firstProvider);
 | 
				
			||||||
 | 
					            setSelectedChatModel(firstModel);
 | 
				
			||||||
 | 
					            localStorage.setItem('chatModelProvider', firstProvider);
 | 
				
			||||||
 | 
					            localStorage.setItem('chatModel', firstModel);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        } else if (
 | 
				
			||||||
 | 
					          currentChatProvider &&
 | 
				
			||||||
 | 
					          (!data.chatModelProviders ||
 | 
				
			||||||
 | 
					            !data.chatModelProviders[currentChatProvider] ||
 | 
				
			||||||
 | 
					            !Array.isArray(data.chatModelProviders[currentChatProvider]) ||
 | 
				
			||||||
 | 
					            data.chatModelProviders[currentChatProvider].length === 0)
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
 | 
					          const firstValidProvider = Object.entries(
 | 
				
			||||||
 | 
					            data.chatModelProviders || {},
 | 
				
			||||||
 | 
					          ).find(
 | 
				
			||||||
 | 
					            ([_, models]) => Array.isArray(models) && models.length > 0,
 | 
				
			||||||
 | 
					          )?.[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          if (firstValidProvider) {
 | 
				
			||||||
 | 
					            setSelectedChatModelProvider(firstValidProvider);
 | 
				
			||||||
 | 
					            setSelectedChatModel(
 | 
				
			||||||
 | 
					              data.chatModelProviders[firstValidProvider][0].name,
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            localStorage.setItem('chatModelProvider', firstValidProvider);
 | 
				
			||||||
 | 
					            localStorage.setItem(
 | 
				
			||||||
 | 
					              'chatModel',
 | 
				
			||||||
 | 
					              data.chatModelProviders[firstValidProvider][0].name,
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            setSelectedChatModelProvider(null);
 | 
				
			||||||
 | 
					            setSelectedChatModel(null);
 | 
				
			||||||
 | 
					            localStorage.removeItem('chatModelProvider');
 | 
				
			||||||
 | 
					            localStorage.removeItem('chatModel');
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const currentEmbeddingProvider = selectedEmbeddingModelProvider;
 | 
				
			||||||
 | 
					        const newEmbeddingProviders = Object.keys(
 | 
				
			||||||
 | 
					          data.embeddingModelProviders || {},
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!currentEmbeddingProvider && newEmbeddingProviders.length > 0) {
 | 
				
			||||||
 | 
					          const firstProvider = newEmbeddingProviders[0];
 | 
				
			||||||
 | 
					          const firstModel =
 | 
				
			||||||
 | 
					            data.embeddingModelProviders[firstProvider]?.[0]?.name;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          if (firstModel) {
 | 
				
			||||||
 | 
					            setSelectedEmbeddingModelProvider(firstProvider);
 | 
				
			||||||
 | 
					            setSelectedEmbeddingModel(firstModel);
 | 
				
			||||||
 | 
					            localStorage.setItem('embeddingModelProvider', firstProvider);
 | 
				
			||||||
 | 
					            localStorage.setItem('embeddingModel', firstModel);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        } else if (
 | 
				
			||||||
 | 
					          currentEmbeddingProvider &&
 | 
				
			||||||
 | 
					          (!data.embeddingModelProviders ||
 | 
				
			||||||
 | 
					            !data.embeddingModelProviders[currentEmbeddingProvider] ||
 | 
				
			||||||
 | 
					            !Array.isArray(
 | 
				
			||||||
 | 
					              data.embeddingModelProviders[currentEmbeddingProvider],
 | 
				
			||||||
 | 
					            ) ||
 | 
				
			||||||
 | 
					            data.embeddingModelProviders[currentEmbeddingProvider].length === 0)
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
 | 
					          const firstValidProvider = Object.entries(
 | 
				
			||||||
 | 
					            data.embeddingModelProviders || {},
 | 
				
			||||||
 | 
					          ).find(
 | 
				
			||||||
 | 
					            ([_, models]) => Array.isArray(models) && models.length > 0,
 | 
				
			||||||
 | 
					          )?.[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          if (firstValidProvider) {
 | 
				
			||||||
 | 
					            setSelectedEmbeddingModelProvider(firstValidProvider);
 | 
				
			||||||
 | 
					            setSelectedEmbeddingModel(
 | 
				
			||||||
 | 
					              data.embeddingModelProviders[firstValidProvider][0].name,
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            localStorage.setItem('embeddingModelProvider', firstValidProvider);
 | 
				
			||||||
 | 
					            localStorage.setItem(
 | 
				
			||||||
 | 
					              'embeddingModel',
 | 
				
			||||||
 | 
					              data.embeddingModelProviders[firstValidProvider][0].name,
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            setSelectedEmbeddingModelProvider(null);
 | 
				
			||||||
 | 
					            setSelectedEmbeddingModel(null);
 | 
				
			||||||
 | 
					            localStorage.removeItem('embeddingModelProvider');
 | 
				
			||||||
 | 
					            localStorage.removeItem('embeddingModel');
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        setConfig(data);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (key === 'automaticImageSearch') {
 | 
				
			||||||
 | 
					        localStorage.setItem('autoImageSearch', value.toString());
 | 
				
			||||||
 | 
					      } else if (key === 'automaticVideoSearch') {
 | 
				
			||||||
 | 
					        localStorage.setItem('autoVideoSearch', value.toString());
 | 
				
			||||||
 | 
					      } else if (key === 'chatModelProvider') {
 | 
				
			||||||
 | 
					        localStorage.setItem('chatModelProvider', value);
 | 
				
			||||||
 | 
					      } else if (key === 'chatModel') {
 | 
				
			||||||
 | 
					        localStorage.setItem('chatModel', value);
 | 
				
			||||||
 | 
					      } else if (key === 'embeddingModelProvider') {
 | 
				
			||||||
 | 
					        localStorage.setItem('embeddingModelProvider', value);
 | 
				
			||||||
 | 
					      } else if (key === 'embeddingModel') {
 | 
				
			||||||
 | 
					        localStorage.setItem('embeddingModel', value);
 | 
				
			||||||
 | 
					      } else if (key === 'systemInstructions') {
 | 
				
			||||||
 | 
					        localStorage.setItem('systemInstructions', value);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } catch (err) {
 | 
				
			||||||
 | 
					      console.error('Failed to save:', err);
 | 
				
			||||||
 | 
					      setConfig((prev) => ({ ...prev! }));
 | 
				
			||||||
 | 
					    } finally {
 | 
				
			||||||
 | 
					      setTimeout(() => {
 | 
				
			||||||
 | 
					        setSavingStates((prev) => ({ ...prev, [key]: false }));
 | 
				
			||||||
 | 
					      }, 500);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div className="max-w-3xl mx-auto">
 | 
				
			||||||
 | 
					      <div className="flex flex-col pt-4">
 | 
				
			||||||
 | 
					        <div className="flex items-center space-x-2">
 | 
				
			||||||
 | 
					          <Link href="/" className="lg:hidden">
 | 
				
			||||||
 | 
					            <ArrowLeft className="text-black/70 dark:text-white/70" />
 | 
				
			||||||
 | 
					          </Link>
 | 
				
			||||||
 | 
					          <div className="flex flex-row space-x-0.5 items-center">
 | 
				
			||||||
 | 
					            <SettingsIcon size={23} />
 | 
				
			||||||
 | 
					            <h1 className="text-3xl font-medium p-2">Settings</h1>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <hr className="border-t border-[#2B2C2C] my-4 w-full" />
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      {isLoading ? (
 | 
				
			||||||
 | 
					        <div className="flex flex-row items-center justify-center min-h-[50vh]">
 | 
				
			||||||
 | 
					          <svg
 | 
				
			||||||
 | 
					            aria-hidden="true"
 | 
				
			||||||
 | 
					            className="w-8 h-8 text-light-200 fill-light-secondary dark:text-[#202020] animate-spin dark:fill-[#ffffff3b]"
 | 
				
			||||||
 | 
					            viewBox="0 0 100 101"
 | 
				
			||||||
 | 
					            fill="none"
 | 
				
			||||||
 | 
					            xmlns="http://www.w3.org/2000/svg"
 | 
				
			||||||
 | 
					          >
 | 
				
			||||||
 | 
					            <path
 | 
				
			||||||
 | 
					              d="M100 50.5908C100.003 78.2051 78.1951 100.003 50.5908 100C22.9765 99.9972 0.997224 78.018 1 50.4037C1.00281 22.7993 22.8108 0.997224 50.4251 1C78.0395 1.00281 100.018 22.8108 100 50.4251ZM9.08164 50.594C9.06312 73.3997 27.7909 92.1272 50.5966 92.1457C73.4023 92.1642 92.1298 73.4365 92.1483 50.6308C92.1669 27.8251 73.4392 9.0973 50.6335 9.07878C27.8278 9.06026 9.10003 27.787 9.08164 50.594Z"
 | 
				
			||||||
 | 
					              fill="currentColor"
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					            <path
 | 
				
			||||||
 | 
					              d="M93.9676 39.0409C96.393 38.4037 97.8624 35.9116 96.9801 33.5533C95.1945 28.8227 92.871 24.3692 90.0681 20.348C85.6237 14.1775 79.4473 9.36872 72.0454 6.45794C64.6435 3.54717 56.3134 2.65431 48.3133 3.89319C45.869 4.27179 44.3768 6.77534 45.014 9.20079C45.6512 11.6262 48.1343 13.0956 50.5786 12.717C56.5073 11.8281 62.5542 12.5399 68.0406 14.7911C73.527 17.0422 78.2187 20.7487 81.5841 25.4923C83.7976 28.5886 85.4467 32.059 86.4416 35.7474C87.1273 38.1189 89.5423 39.6781 91.9676 39.0409Z"
 | 
				
			||||||
 | 
					              fill="currentFill"
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					          </svg>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      ) : (
 | 
				
			||||||
 | 
					        config && (
 | 
				
			||||||
 | 
					          <div className="flex flex-col space-y-6 pb-28 lg:pb-8">
 | 
				
			||||||
 | 
					            <SettingsSection title="Appearance">
 | 
				
			||||||
 | 
					              <div className="flex flex-col space-y-1">
 | 
				
			||||||
 | 
					                <p className="text-black/70 dark:text-white/70 text-sm">
 | 
				
			||||||
 | 
					                  Theme
 | 
				
			||||||
 | 
					                </p>
 | 
				
			||||||
 | 
					                <ThemeSwitcher />
 | 
				
			||||||
 | 
					              </div>
 | 
				
			||||||
 | 
					            </SettingsSection>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <SettingsSection title="Automatic Search">
 | 
				
			||||||
 | 
					              <div className="flex flex-col space-y-4">
 | 
				
			||||||
 | 
					                <div className="flex items-center justify-between p-3 bg-light-secondary dark:bg-dark-secondary rounded-lg hover:bg-light-200 dark:hover:bg-dark-200 transition-colors">
 | 
				
			||||||
 | 
					                  <div className="flex items-center space-x-3">
 | 
				
			||||||
 | 
					                    <div className="p-2 bg-light-200 dark:bg-dark-200 rounded-lg">
 | 
				
			||||||
 | 
					                      <ImagesIcon
 | 
				
			||||||
 | 
					                        size={18}
 | 
				
			||||||
 | 
					                        className="text-black/70 dark:text-white/70"
 | 
				
			||||||
 | 
					                      />
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    <div>
 | 
				
			||||||
 | 
					                      <p className="text-sm text-black/90 dark:text-white/90 font-medium">
 | 
				
			||||||
 | 
					                        Automatic Image Search
 | 
				
			||||||
 | 
					                      </p>
 | 
				
			||||||
 | 
					                      <p className="text-xs text-black/60 dark:text-white/60 mt-0.5">
 | 
				
			||||||
 | 
					                        Automatically search for relevant images in chat
 | 
				
			||||||
 | 
					                        responses
 | 
				
			||||||
 | 
					                      </p>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                  </div>
 | 
				
			||||||
 | 
					                  <Switch
 | 
				
			||||||
 | 
					                    checked={automaticImageSearch}
 | 
				
			||||||
 | 
					                    onChange={(checked) => {
 | 
				
			||||||
 | 
					                      setAutomaticImageSearch(checked);
 | 
				
			||||||
 | 
					                      saveConfig('automaticImageSearch', checked);
 | 
				
			||||||
 | 
					                    }}
 | 
				
			||||||
 | 
					                    className={cn(
 | 
				
			||||||
 | 
					                      automaticImageSearch
 | 
				
			||||||
 | 
					                        ? 'bg-[#24A0ED]'
 | 
				
			||||||
 | 
					                        : 'bg-light-200 dark:bg-dark-200',
 | 
				
			||||||
 | 
					                      'relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none',
 | 
				
			||||||
 | 
					                    )}
 | 
				
			||||||
 | 
					                  >
 | 
				
			||||||
 | 
					                    <span
 | 
				
			||||||
 | 
					                      className={cn(
 | 
				
			||||||
 | 
					                        automaticImageSearch
 | 
				
			||||||
 | 
					                          ? 'translate-x-6'
 | 
				
			||||||
 | 
					                          : 'translate-x-1',
 | 
				
			||||||
 | 
					                        'inline-block h-4 w-4 transform rounded-full bg-white transition-transform',
 | 
				
			||||||
 | 
					                      )}
 | 
				
			||||||
 | 
					                    />
 | 
				
			||||||
 | 
					                  </Switch>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <div className="flex items-center justify-between p-3 bg-light-secondary dark:bg-dark-secondary rounded-lg hover:bg-light-200 dark:hover:bg-dark-200 transition-colors">
 | 
				
			||||||
 | 
					                  <div className="flex items-center space-x-3">
 | 
				
			||||||
 | 
					                    <div className="p-2 bg-light-200 dark:bg-dark-200 rounded-lg">
 | 
				
			||||||
 | 
					                      <VideoIcon
 | 
				
			||||||
 | 
					                        size={18}
 | 
				
			||||||
 | 
					                        className="text-black/70 dark:text-white/70"
 | 
				
			||||||
 | 
					                      />
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    <div>
 | 
				
			||||||
 | 
					                      <p className="text-sm text-black/90 dark:text-white/90 font-medium">
 | 
				
			||||||
 | 
					                        Automatic Video Search
 | 
				
			||||||
 | 
					                      </p>
 | 
				
			||||||
 | 
					                      <p className="text-xs text-black/60 dark:text-white/60 mt-0.5">
 | 
				
			||||||
 | 
					                        Automatically search for relevant videos in chat
 | 
				
			||||||
 | 
					                        responses
 | 
				
			||||||
 | 
					                      </p>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                  </div>
 | 
				
			||||||
 | 
					                  <Switch
 | 
				
			||||||
 | 
					                    checked={automaticVideoSearch}
 | 
				
			||||||
 | 
					                    onChange={(checked) => {
 | 
				
			||||||
 | 
					                      setAutomaticVideoSearch(checked);
 | 
				
			||||||
 | 
					                      saveConfig('automaticVideoSearch', checked);
 | 
				
			||||||
 | 
					                    }}
 | 
				
			||||||
 | 
					                    className={cn(
 | 
				
			||||||
 | 
					                      automaticVideoSearch
 | 
				
			||||||
 | 
					                        ? 'bg-[#24A0ED]'
 | 
				
			||||||
 | 
					                        : 'bg-light-200 dark:bg-dark-200',
 | 
				
			||||||
 | 
					                      'relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none',
 | 
				
			||||||
 | 
					                    )}
 | 
				
			||||||
 | 
					                  >
 | 
				
			||||||
 | 
					                    <span
 | 
				
			||||||
 | 
					                      className={cn(
 | 
				
			||||||
 | 
					                        automaticVideoSearch
 | 
				
			||||||
 | 
					                          ? 'translate-x-6'
 | 
				
			||||||
 | 
					                          : 'translate-x-1',
 | 
				
			||||||
 | 
					                        'inline-block h-4 w-4 transform rounded-full bg-white transition-transform',
 | 
				
			||||||
 | 
					                      )}
 | 
				
			||||||
 | 
					                    />
 | 
				
			||||||
 | 
					                  </Switch>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					              </div>
 | 
				
			||||||
 | 
					            </SettingsSection>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <SettingsSection title="System Instructions">
 | 
				
			||||||
 | 
					              <div className="flex flex-col space-y-4">
 | 
				
			||||||
 | 
					                <Textarea
 | 
				
			||||||
 | 
					                  value={systemInstructions}
 | 
				
			||||||
 | 
					                  isSaving={savingStates['systemInstructions']}
 | 
				
			||||||
 | 
					                  onChange={(e) => {
 | 
				
			||||||
 | 
					                    setSystemInstructions(e.target.value);
 | 
				
			||||||
 | 
					                  }}
 | 
				
			||||||
 | 
					                  onSave={(value) => saveConfig('systemInstructions', value)}
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					              </div>
 | 
				
			||||||
 | 
					            </SettingsSection>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <SettingsSection title="Model Settings">
 | 
				
			||||||
 | 
					              {config.chatModelProviders && (
 | 
				
			||||||
 | 
					                <div className="flex flex-col space-y-4">
 | 
				
			||||||
 | 
					                  <div className="flex flex-col space-y-1">
 | 
				
			||||||
 | 
					                    <p className="text-black/70 dark:text-white/70 text-sm">
 | 
				
			||||||
 | 
					                      Chat Model Provider
 | 
				
			||||||
 | 
					                    </p>
 | 
				
			||||||
 | 
					                    <Select
 | 
				
			||||||
 | 
					                      value={selectedChatModelProvider ?? undefined}
 | 
				
			||||||
 | 
					                      onChange={(e) => {
 | 
				
			||||||
 | 
					                        const value = e.target.value;
 | 
				
			||||||
 | 
					                        setSelectedChatModelProvider(value);
 | 
				
			||||||
 | 
					                        saveConfig('chatModelProvider', value);
 | 
				
			||||||
 | 
					                        const firstModel =
 | 
				
			||||||
 | 
					                          config.chatModelProviders[value]?.[0]?.name;
 | 
				
			||||||
 | 
					                        if (firstModel) {
 | 
				
			||||||
 | 
					                          setSelectedChatModel(firstModel);
 | 
				
			||||||
 | 
					                          saveConfig('chatModel', firstModel);
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                      }}
 | 
				
			||||||
 | 
					                      options={Object.keys(config.chatModelProviders).map(
 | 
				
			||||||
 | 
					                        (provider) => ({
 | 
				
			||||||
 | 
					                          value: provider,
 | 
				
			||||||
 | 
					                          label:
 | 
				
			||||||
 | 
					                            provider.charAt(0).toUpperCase() +
 | 
				
			||||||
 | 
					                            provider.slice(1),
 | 
				
			||||||
 | 
					                        }),
 | 
				
			||||||
 | 
					                      )}
 | 
				
			||||||
 | 
					                    />
 | 
				
			||||||
 | 
					                  </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                  {selectedChatModelProvider &&
 | 
				
			||||||
 | 
					                    selectedChatModelProvider != 'custom_openai' && (
 | 
				
			||||||
 | 
					                      <div className="flex flex-col space-y-1">
 | 
				
			||||||
 | 
					                        <p className="text-black/70 dark:text-white/70 text-sm">
 | 
				
			||||||
 | 
					                          Chat Model
 | 
				
			||||||
 | 
					                        </p>
 | 
				
			||||||
 | 
					                        <Select
 | 
				
			||||||
 | 
					                          value={selectedChatModel ?? undefined}
 | 
				
			||||||
 | 
					                          onChange={(e) => {
 | 
				
			||||||
 | 
					                            const value = e.target.value;
 | 
				
			||||||
 | 
					                            setSelectedChatModel(value);
 | 
				
			||||||
 | 
					                            saveConfig('chatModel', value);
 | 
				
			||||||
 | 
					                          }}
 | 
				
			||||||
 | 
					                          options={(() => {
 | 
				
			||||||
 | 
					                            const chatModelProvider =
 | 
				
			||||||
 | 
					                              config.chatModelProviders[
 | 
				
			||||||
 | 
					                                selectedChatModelProvider
 | 
				
			||||||
 | 
					                              ];
 | 
				
			||||||
 | 
					                            return chatModelProvider
 | 
				
			||||||
 | 
					                              ? chatModelProvider.length > 0
 | 
				
			||||||
 | 
					                                ? chatModelProvider.map((model) => ({
 | 
				
			||||||
 | 
					                                    value: model.name,
 | 
				
			||||||
 | 
					                                    label: model.displayName,
 | 
				
			||||||
 | 
					                                  }))
 | 
				
			||||||
 | 
					                                : [
 | 
				
			||||||
 | 
					                                    {
 | 
				
			||||||
 | 
					                                      value: '',
 | 
				
			||||||
 | 
					                                      label: 'No models available',
 | 
				
			||||||
 | 
					                                      disabled: true,
 | 
				
			||||||
 | 
					                                    },
 | 
				
			||||||
 | 
					                                  ]
 | 
				
			||||||
 | 
					                              : [
 | 
				
			||||||
 | 
					                                  {
 | 
				
			||||||
 | 
					                                    value: '',
 | 
				
			||||||
 | 
					                                    label:
 | 
				
			||||||
 | 
					                                      'Invalid provider, please check backend logs',
 | 
				
			||||||
 | 
					                                    disabled: true,
 | 
				
			||||||
 | 
					                                  },
 | 
				
			||||||
 | 
					                                ];
 | 
				
			||||||
 | 
					                          })()}
 | 
				
			||||||
 | 
					                        />
 | 
				
			||||||
 | 
					                      </div>
 | 
				
			||||||
 | 
					                    )}
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					              )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              {selectedChatModelProvider &&
 | 
				
			||||||
 | 
					                selectedChatModelProvider === 'custom_openai' && (
 | 
				
			||||||
 | 
					                  <div className="flex flex-col space-y-4">
 | 
				
			||||||
 | 
					                    <div className="flex flex-col space-y-1">
 | 
				
			||||||
 | 
					                      <p className="text-black/70 dark:text-white/70 text-sm">
 | 
				
			||||||
 | 
					                        Model Name
 | 
				
			||||||
 | 
					                      </p>
 | 
				
			||||||
 | 
					                      <Input
 | 
				
			||||||
 | 
					                        type="text"
 | 
				
			||||||
 | 
					                        placeholder="Model name"
 | 
				
			||||||
 | 
					                        value={config.customOpenaiModelName}
 | 
				
			||||||
 | 
					                        isSaving={savingStates['customOpenaiModelName']}
 | 
				
			||||||
 | 
					                        onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
 | 
				
			||||||
 | 
					                          setConfig((prev) => ({
 | 
				
			||||||
 | 
					                            ...prev!,
 | 
				
			||||||
 | 
					                            customOpenaiModelName: e.target.value,
 | 
				
			||||||
 | 
					                          }));
 | 
				
			||||||
 | 
					                        }}
 | 
				
			||||||
 | 
					                        onSave={(value) =>
 | 
				
			||||||
 | 
					                          saveConfig('customOpenaiModelName', value)
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                      />
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    <div className="flex flex-col space-y-1">
 | 
				
			||||||
 | 
					                      <p className="text-black/70 dark:text-white/70 text-sm">
 | 
				
			||||||
 | 
					                        Custom OpenAI API Key
 | 
				
			||||||
 | 
					                      </p>
 | 
				
			||||||
 | 
					                      <Input
 | 
				
			||||||
 | 
					                        type="text"
 | 
				
			||||||
 | 
					                        placeholder="Custom OpenAI API Key"
 | 
				
			||||||
 | 
					                        value={config.customOpenaiApiKey}
 | 
				
			||||||
 | 
					                        isSaving={savingStates['customOpenaiApiKey']}
 | 
				
			||||||
 | 
					                        onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
 | 
				
			||||||
 | 
					                          setConfig((prev) => ({
 | 
				
			||||||
 | 
					                            ...prev!,
 | 
				
			||||||
 | 
					                            customOpenaiApiKey: e.target.value,
 | 
				
			||||||
 | 
					                          }));
 | 
				
			||||||
 | 
					                        }}
 | 
				
			||||||
 | 
					                        onSave={(value) =>
 | 
				
			||||||
 | 
					                          saveConfig('customOpenaiApiKey', value)
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                      />
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    <div className="flex flex-col space-y-1">
 | 
				
			||||||
 | 
					                      <p className="text-black/70 dark:text-white/70 text-sm">
 | 
				
			||||||
 | 
					                        Custom OpenAI Base URL
 | 
				
			||||||
 | 
					                      </p>
 | 
				
			||||||
 | 
					                      <Input
 | 
				
			||||||
 | 
					                        type="text"
 | 
				
			||||||
 | 
					                        placeholder="Custom OpenAI Base URL"
 | 
				
			||||||
 | 
					                        value={config.customOpenaiApiUrl}
 | 
				
			||||||
 | 
					                        isSaving={savingStates['customOpenaiApiUrl']}
 | 
				
			||||||
 | 
					                        onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
 | 
				
			||||||
 | 
					                          setConfig((prev) => ({
 | 
				
			||||||
 | 
					                            ...prev!,
 | 
				
			||||||
 | 
					                            customOpenaiApiUrl: e.target.value,
 | 
				
			||||||
 | 
					                          }));
 | 
				
			||||||
 | 
					                        }}
 | 
				
			||||||
 | 
					                        onSave={(value) =>
 | 
				
			||||||
 | 
					                          saveConfig('customOpenaiApiUrl', value)
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                      />
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                  </div>
 | 
				
			||||||
 | 
					                )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              {config.embeddingModelProviders && (
 | 
				
			||||||
 | 
					                <div className="flex flex-col space-y-4 mt-4 pt-4 border-t border-light-200 dark:border-dark-200">
 | 
				
			||||||
 | 
					                  <div className="flex flex-col space-y-1">
 | 
				
			||||||
 | 
					                    <p className="text-black/70 dark:text-white/70 text-sm">
 | 
				
			||||||
 | 
					                      Embedding Model Provider
 | 
				
			||||||
 | 
					                    </p>
 | 
				
			||||||
 | 
					                    <Select
 | 
				
			||||||
 | 
					                      value={selectedEmbeddingModelProvider ?? undefined}
 | 
				
			||||||
 | 
					                      onChange={(e) => {
 | 
				
			||||||
 | 
					                        const value = e.target.value;
 | 
				
			||||||
 | 
					                        setSelectedEmbeddingModelProvider(value);
 | 
				
			||||||
 | 
					                        saveConfig('embeddingModelProvider', value);
 | 
				
			||||||
 | 
					                        const firstModel =
 | 
				
			||||||
 | 
					                          config.embeddingModelProviders[value]?.[0]?.name;
 | 
				
			||||||
 | 
					                        if (firstModel) {
 | 
				
			||||||
 | 
					                          setSelectedEmbeddingModel(firstModel);
 | 
				
			||||||
 | 
					                          saveConfig('embeddingModel', firstModel);
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                      }}
 | 
				
			||||||
 | 
					                      options={Object.keys(config.embeddingModelProviders).map(
 | 
				
			||||||
 | 
					                        (provider) => ({
 | 
				
			||||||
 | 
					                          value: provider,
 | 
				
			||||||
 | 
					                          label:
 | 
				
			||||||
 | 
					                            provider.charAt(0).toUpperCase() +
 | 
				
			||||||
 | 
					                            provider.slice(1),
 | 
				
			||||||
 | 
					                        }),
 | 
				
			||||||
 | 
					                      )}
 | 
				
			||||||
 | 
					                    />
 | 
				
			||||||
 | 
					                  </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                  {selectedEmbeddingModelProvider && (
 | 
				
			||||||
 | 
					                    <div className="flex flex-col space-y-1">
 | 
				
			||||||
 | 
					                      <p className="text-black/70 dark:text-white/70 text-sm">
 | 
				
			||||||
 | 
					                        Embedding Model
 | 
				
			||||||
 | 
					                      </p>
 | 
				
			||||||
 | 
					                      <Select
 | 
				
			||||||
 | 
					                        value={selectedEmbeddingModel ?? undefined}
 | 
				
			||||||
 | 
					                        onChange={(e) => {
 | 
				
			||||||
 | 
					                          const value = e.target.value;
 | 
				
			||||||
 | 
					                          setSelectedEmbeddingModel(value);
 | 
				
			||||||
 | 
					                          saveConfig('embeddingModel', value);
 | 
				
			||||||
 | 
					                        }}
 | 
				
			||||||
 | 
					                        options={(() => {
 | 
				
			||||||
 | 
					                          const embeddingModelProvider =
 | 
				
			||||||
 | 
					                            config.embeddingModelProviders[
 | 
				
			||||||
 | 
					                              selectedEmbeddingModelProvider
 | 
				
			||||||
 | 
					                            ];
 | 
				
			||||||
 | 
					                          return embeddingModelProvider
 | 
				
			||||||
 | 
					                            ? embeddingModelProvider.length > 0
 | 
				
			||||||
 | 
					                              ? embeddingModelProvider.map((model) => ({
 | 
				
			||||||
 | 
					                                  value: model.name,
 | 
				
			||||||
 | 
					                                  label: model.displayName,
 | 
				
			||||||
 | 
					                                }))
 | 
				
			||||||
 | 
					                              : [
 | 
				
			||||||
 | 
					                                  {
 | 
				
			||||||
 | 
					                                    value: '',
 | 
				
			||||||
 | 
					                                    label: 'No models available',
 | 
				
			||||||
 | 
					                                    disabled: true,
 | 
				
			||||||
 | 
					                                  },
 | 
				
			||||||
 | 
					                                ]
 | 
				
			||||||
 | 
					                            : [
 | 
				
			||||||
 | 
					                                {
 | 
				
			||||||
 | 
					                                  value: '',
 | 
				
			||||||
 | 
					                                  label:
 | 
				
			||||||
 | 
					                                    'Invalid provider, please check backend logs',
 | 
				
			||||||
 | 
					                                  disabled: true,
 | 
				
			||||||
 | 
					                                },
 | 
				
			||||||
 | 
					                              ];
 | 
				
			||||||
 | 
					                        })()}
 | 
				
			||||||
 | 
					                      />
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                  )}
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					              )}
 | 
				
			||||||
 | 
					            </SettingsSection>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <SettingsSection title="API Keys">
 | 
				
			||||||
 | 
					              <div className="flex flex-col space-y-4">
 | 
				
			||||||
 | 
					                <div className="flex flex-col space-y-1">
 | 
				
			||||||
 | 
					                  <p className="text-black/70 dark:text-white/70 text-sm">
 | 
				
			||||||
 | 
					                    OpenAI API Key
 | 
				
			||||||
 | 
					                  </p>
 | 
				
			||||||
 | 
					                  <Input
 | 
				
			||||||
 | 
					                    type="text"
 | 
				
			||||||
 | 
					                    placeholder="OpenAI API Key"
 | 
				
			||||||
 | 
					                    value={config.openaiApiKey}
 | 
				
			||||||
 | 
					                    isSaving={savingStates['openaiApiKey']}
 | 
				
			||||||
 | 
					                    onChange={(e) => {
 | 
				
			||||||
 | 
					                      setConfig((prev) => ({
 | 
				
			||||||
 | 
					                        ...prev!,
 | 
				
			||||||
 | 
					                        openaiApiKey: e.target.value,
 | 
				
			||||||
 | 
					                      }));
 | 
				
			||||||
 | 
					                    }}
 | 
				
			||||||
 | 
					                    onSave={(value) => saveConfig('openaiApiKey', value)}
 | 
				
			||||||
 | 
					                  />
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <div className="flex flex-col space-y-1">
 | 
				
			||||||
 | 
					                  <p className="text-black/70 dark:text-white/70 text-sm">
 | 
				
			||||||
 | 
					                    Ollama API URL
 | 
				
			||||||
 | 
					                  </p>
 | 
				
			||||||
 | 
					                  <Input
 | 
				
			||||||
 | 
					                    type="text"
 | 
				
			||||||
 | 
					                    placeholder="Ollama API URL"
 | 
				
			||||||
 | 
					                    value={config.ollamaApiUrl}
 | 
				
			||||||
 | 
					                    isSaving={savingStates['ollamaApiUrl']}
 | 
				
			||||||
 | 
					                    onChange={(e) => {
 | 
				
			||||||
 | 
					                      setConfig((prev) => ({
 | 
				
			||||||
 | 
					                        ...prev!,
 | 
				
			||||||
 | 
					                        ollamaApiUrl: e.target.value,
 | 
				
			||||||
 | 
					                      }));
 | 
				
			||||||
 | 
					                    }}
 | 
				
			||||||
 | 
					                    onSave={(value) => saveConfig('ollamaApiUrl', value)}
 | 
				
			||||||
 | 
					                  />
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <div className="flex flex-col space-y-1">
 | 
				
			||||||
 | 
					                  <p className="text-black/70 dark:text-white/70 text-sm">
 | 
				
			||||||
 | 
					                    GROQ API Key
 | 
				
			||||||
 | 
					                  </p>
 | 
				
			||||||
 | 
					                  <Input
 | 
				
			||||||
 | 
					                    type="text"
 | 
				
			||||||
 | 
					                    placeholder="GROQ API Key"
 | 
				
			||||||
 | 
					                    value={config.groqApiKey}
 | 
				
			||||||
 | 
					                    isSaving={savingStates['groqApiKey']}
 | 
				
			||||||
 | 
					                    onChange={(e) => {
 | 
				
			||||||
 | 
					                      setConfig((prev) => ({
 | 
				
			||||||
 | 
					                        ...prev!,
 | 
				
			||||||
 | 
					                        groqApiKey: e.target.value,
 | 
				
			||||||
 | 
					                      }));
 | 
				
			||||||
 | 
					                    }}
 | 
				
			||||||
 | 
					                    onSave={(value) => saveConfig('groqApiKey', value)}
 | 
				
			||||||
 | 
					                  />
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <div className="flex flex-col space-y-1">
 | 
				
			||||||
 | 
					                  <p className="text-black/70 dark:text-white/70 text-sm">
 | 
				
			||||||
 | 
					                    Anthropic API Key
 | 
				
			||||||
 | 
					                  </p>
 | 
				
			||||||
 | 
					                  <Input
 | 
				
			||||||
 | 
					                    type="text"
 | 
				
			||||||
 | 
					                    placeholder="Anthropic API key"
 | 
				
			||||||
 | 
					                    value={config.anthropicApiKey}
 | 
				
			||||||
 | 
					                    isSaving={savingStates['anthropicApiKey']}
 | 
				
			||||||
 | 
					                    onChange={(e) => {
 | 
				
			||||||
 | 
					                      setConfig((prev) => ({
 | 
				
			||||||
 | 
					                        ...prev!,
 | 
				
			||||||
 | 
					                        anthropicApiKey: e.target.value,
 | 
				
			||||||
 | 
					                      }));
 | 
				
			||||||
 | 
					                    }}
 | 
				
			||||||
 | 
					                    onSave={(value) => saveConfig('anthropicApiKey', value)}
 | 
				
			||||||
 | 
					                  />
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <div className="flex flex-col space-y-1">
 | 
				
			||||||
 | 
					                  <p className="text-black/70 dark:text-white/70 text-sm">
 | 
				
			||||||
 | 
					                    Gemini API Key
 | 
				
			||||||
 | 
					                  </p>
 | 
				
			||||||
 | 
					                  <Input
 | 
				
			||||||
 | 
					                    type="text"
 | 
				
			||||||
 | 
					                    placeholder="Gemini API key"
 | 
				
			||||||
 | 
					                    value={config.geminiApiKey}
 | 
				
			||||||
 | 
					                    isSaving={savingStates['geminiApiKey']}
 | 
				
			||||||
 | 
					                    onChange={(e) => {
 | 
				
			||||||
 | 
					                      setConfig((prev) => ({
 | 
				
			||||||
 | 
					                        ...prev!,
 | 
				
			||||||
 | 
					                        geminiApiKey: e.target.value,
 | 
				
			||||||
 | 
					                      }));
 | 
				
			||||||
 | 
					                    }}
 | 
				
			||||||
 | 
					                    onSave={(value) => saveConfig('geminiApiKey', value)}
 | 
				
			||||||
 | 
					                  />
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <div className="flex flex-col space-y-1">
 | 
				
			||||||
 | 
					                  <p className="text-black/70 dark:text-white/70 text-sm">
 | 
				
			||||||
 | 
					                    Deepseek API Key
 | 
				
			||||||
 | 
					                  </p>
 | 
				
			||||||
 | 
					                  <Input
 | 
				
			||||||
 | 
					                    type="text"
 | 
				
			||||||
 | 
					                    placeholder="Deepseek API Key"
 | 
				
			||||||
 | 
					                    value={config.deepseekApiKey}
 | 
				
			||||||
 | 
					                    isSaving={savingStates['deepseekApiKey']}
 | 
				
			||||||
 | 
					                    onChange={(e) => {
 | 
				
			||||||
 | 
					                      setConfig((prev) => ({
 | 
				
			||||||
 | 
					                        ...prev!,
 | 
				
			||||||
 | 
					                        deepseekApiKey: e.target.value,
 | 
				
			||||||
 | 
					                      }));
 | 
				
			||||||
 | 
					                    }}
 | 
				
			||||||
 | 
					                    onSave={(value) => saveConfig('deepseekApiKey', value)}
 | 
				
			||||||
 | 
					                  />
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					              </div>
 | 
				
			||||||
 | 
					            </SettingsSection>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default Page;
 | 
				
			||||||
@@ -2,13 +2,31 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import { Fragment, useEffect, useRef, useState } from 'react';
 | 
					import { Fragment, useEffect, useRef, useState } from 'react';
 | 
				
			||||||
import MessageInput from './MessageInput';
 | 
					import MessageInput from './MessageInput';
 | 
				
			||||||
 | 
					import { File, Message } from './ChatWindow';
 | 
				
			||||||
import MessageBox from './MessageBox';
 | 
					import MessageBox from './MessageBox';
 | 
				
			||||||
import MessageBoxLoading from './MessageBoxLoading';
 | 
					import MessageBoxLoading from './MessageBoxLoading';
 | 
				
			||||||
import { useChat } from '@/lib/hooks/useChat';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const Chat = () => {
 | 
					 | 
				
			||||||
  const { sections, chatTurns, loading, messageAppeared } = useChat();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Chat = ({
 | 
				
			||||||
 | 
					  loading,
 | 
				
			||||||
 | 
					  messages,
 | 
				
			||||||
 | 
					  sendMessage,
 | 
				
			||||||
 | 
					  messageAppeared,
 | 
				
			||||||
 | 
					  rewrite,
 | 
				
			||||||
 | 
					  fileIds,
 | 
				
			||||||
 | 
					  setFileIds,
 | 
				
			||||||
 | 
					  files,
 | 
				
			||||||
 | 
					  setFiles,
 | 
				
			||||||
 | 
					}: {
 | 
				
			||||||
 | 
					  messages: Message[];
 | 
				
			||||||
 | 
					  sendMessage: (message: string) => void;
 | 
				
			||||||
 | 
					  loading: boolean;
 | 
				
			||||||
 | 
					  messageAppeared: boolean;
 | 
				
			||||||
 | 
					  rewrite: (messageId: string) => void;
 | 
				
			||||||
 | 
					  fileIds: string[];
 | 
				
			||||||
 | 
					  setFileIds: (fileIds: string[]) => void;
 | 
				
			||||||
 | 
					  files: File[];
 | 
				
			||||||
 | 
					  setFiles: (files: File[]) => void;
 | 
				
			||||||
 | 
					}) => {
 | 
				
			||||||
  const [dividerWidth, setDividerWidth] = useState(0);
 | 
					  const [dividerWidth, setDividerWidth] = useState(0);
 | 
				
			||||||
  const dividerRef = useRef<HTMLDivElement | null>(null);
 | 
					  const dividerRef = useRef<HTMLDivElement | null>(null);
 | 
				
			||||||
  const messageEnd = useRef<HTMLDivElement | null>(null);
 | 
					  const messageEnd = useRef<HTMLDivElement | null>(null);
 | 
				
			||||||
@@ -16,7 +34,7 @@ const Chat = () => {
 | 
				
			|||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
    const updateDividerWidth = () => {
 | 
					    const updateDividerWidth = () => {
 | 
				
			||||||
      if (dividerRef.current) {
 | 
					      if (dividerRef.current) {
 | 
				
			||||||
        setDividerWidth(dividerRef.current.offsetWidth);
 | 
					        setDividerWidth(dividerRef.current.scrollWidth);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -27,45 +45,41 @@ const Chat = () => {
 | 
				
			|||||||
    return () => {
 | 
					    return () => {
 | 
				
			||||||
      window.removeEventListener('resize', updateDividerWidth);
 | 
					      window.removeEventListener('resize', updateDividerWidth);
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  }, []);
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
    const scroll = () => {
 | 
					    const scroll = () => {
 | 
				
			||||||
      messageEnd.current?.scrollIntoView({ behavior: 'auto' });
 | 
					      messageEnd.current?.scrollIntoView({ behavior: 'smooth' });
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (chatTurns.length === 1) {
 | 
					    if (messages.length === 1) {
 | 
				
			||||||
      document.title = `${chatTurns[0].content.substring(0, 30)} - Perplexica`;
 | 
					      document.title = `${messages[0].content.substring(0, 30)} - Perplexica`;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const messageEndBottom =
 | 
					    if (messages[messages.length - 1]?.role == 'user') {
 | 
				
			||||||
      messageEnd.current?.getBoundingClientRect().bottom ?? 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const distanceFromMessageEnd = window.innerHeight - messageEndBottom;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (distanceFromMessageEnd >= -100) {
 | 
					 | 
				
			||||||
      scroll();
 | 
					      scroll();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					  }, [messages]);
 | 
				
			||||||
    if (chatTurns[chatTurns.length - 1]?.role === 'user') {
 | 
					 | 
				
			||||||
      scroll();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }, [chatTurns]);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <div className="flex flex-col space-y-6 pt-8 pb-44 lg:pb-32 sm:mx-4 md:mx-8">
 | 
					    <div className="flex flex-col space-y-6 pt-8 pb-44 lg:pb-32 sm:mx-4 md:mx-8">
 | 
				
			||||||
      {sections.map((section, i) => {
 | 
					      {messages.map((msg, i) => {
 | 
				
			||||||
        const isLast = i === sections.length - 1;
 | 
					        const isLast = i === messages.length - 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return (
 | 
					        return (
 | 
				
			||||||
          <Fragment key={section.userMessage.messageId}>
 | 
					          <Fragment key={msg.messageId}>
 | 
				
			||||||
            <MessageBox
 | 
					            <MessageBox
 | 
				
			||||||
              section={section}
 | 
					              key={i}
 | 
				
			||||||
              sectionIndex={i}
 | 
					              message={msg}
 | 
				
			||||||
 | 
					              messageIndex={i}
 | 
				
			||||||
 | 
					              history={messages}
 | 
				
			||||||
 | 
					              loading={loading}
 | 
				
			||||||
              dividerRef={isLast ? dividerRef : undefined}
 | 
					              dividerRef={isLast ? dividerRef : undefined}
 | 
				
			||||||
              isLast={isLast}
 | 
					              isLast={isLast}
 | 
				
			||||||
 | 
					              rewrite={rewrite}
 | 
				
			||||||
 | 
					              sendMessage={sendMessage}
 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
            {!isLast && (
 | 
					            {!isLast && msg.role === 'assistant' && (
 | 
				
			||||||
              <div className="h-px w-full bg-light-secondary dark:bg-dark-secondary" />
 | 
					              <div className="h-px w-full bg-light-secondary dark:bg-dark-secondary" />
 | 
				
			||||||
            )}
 | 
					            )}
 | 
				
			||||||
          </Fragment>
 | 
					          </Fragment>
 | 
				
			||||||
@@ -78,7 +92,14 @@ const Chat = () => {
 | 
				
			|||||||
          className="bottom-24 lg:bottom-10 fixed z-40"
 | 
					          className="bottom-24 lg:bottom-10 fixed z-40"
 | 
				
			||||||
          style={{ width: dividerWidth }}
 | 
					          style={{ width: dividerWidth }}
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
          <MessageInput />
 | 
					          <MessageInput
 | 
				
			||||||
 | 
					            loading={loading}
 | 
				
			||||||
 | 
					            sendMessage={sendMessage}
 | 
				
			||||||
 | 
					            fileIds={fileIds}
 | 
				
			||||||
 | 
					            setFileIds={setFileIds}
 | 
				
			||||||
 | 
					            files={files}
 | 
				
			||||||
 | 
					            setFiles={setFiles}
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      )}
 | 
					      )}
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,49 +1,27 @@
 | 
				
			|||||||
'use client';
 | 
					'use client';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { useEffect, useRef, useState } from 'react';
 | 
				
			||||||
import { Document } from '@langchain/core/documents';
 | 
					import { Document } from '@langchain/core/documents';
 | 
				
			||||||
import Navbar from './Navbar';
 | 
					import Navbar from './Navbar';
 | 
				
			||||||
import Chat from './Chat';
 | 
					import Chat from './Chat';
 | 
				
			||||||
import EmptyChat from './EmptyChat';
 | 
					import EmptyChat from './EmptyChat';
 | 
				
			||||||
 | 
					import crypto from 'crypto';
 | 
				
			||||||
 | 
					import { toast } from 'sonner';
 | 
				
			||||||
 | 
					import { useSearchParams } from 'next/navigation';
 | 
				
			||||||
 | 
					import { getSuggestions } from '@/lib/actions';
 | 
				
			||||||
import { Settings } from 'lucide-react';
 | 
					import { Settings } from 'lucide-react';
 | 
				
			||||||
import Link from 'next/link';
 | 
					import Link from 'next/link';
 | 
				
			||||||
import NextError from 'next/error';
 | 
					import NextError from 'next/error';
 | 
				
			||||||
import { useChat } from '@/lib/hooks/useChat';
 | 
					 | 
				
			||||||
import Loader from './ui/Loader';
 | 
					 | 
				
			||||||
import SettingsButtonMobile from './Settings/SettingsButtonMobile';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface BaseMessage {
 | 
					export type Message = {
 | 
				
			||||||
  chatId: string;
 | 
					 | 
				
			||||||
  messageId: string;
 | 
					  messageId: string;
 | 
				
			||||||
 | 
					  chatId: string;
 | 
				
			||||||
  createdAt: Date;
 | 
					  createdAt: Date;
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export interface AssistantMessage extends BaseMessage {
 | 
					 | 
				
			||||||
  role: 'assistant';
 | 
					 | 
				
			||||||
  content: string;
 | 
					  content: string;
 | 
				
			||||||
 | 
					  role: 'user' | 'assistant';
 | 
				
			||||||
  suggestions?: string[];
 | 
					  suggestions?: string[];
 | 
				
			||||||
}
 | 
					  sources?: Document[];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
export interface UserMessage extends BaseMessage {
 | 
					 | 
				
			||||||
  role: 'user';
 | 
					 | 
				
			||||||
  content: string;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export interface SourceMessage extends BaseMessage {
 | 
					 | 
				
			||||||
  role: 'source';
 | 
					 | 
				
			||||||
  sources: Document[];
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export interface SuggestionMessage extends BaseMessage {
 | 
					 | 
				
			||||||
  role: 'suggestion';
 | 
					 | 
				
			||||||
  suggestions: string[];
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export type Message =
 | 
					 | 
				
			||||||
  | AssistantMessage
 | 
					 | 
				
			||||||
  | UserMessage
 | 
					 | 
				
			||||||
  | SourceMessage
 | 
					 | 
				
			||||||
  | SuggestionMessage;
 | 
					 | 
				
			||||||
export type ChatTurn = UserMessage | AssistantMessage;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface File {
 | 
					export interface File {
 | 
				
			||||||
  fileName: string;
 | 
					  fileName: string;
 | 
				
			||||||
@@ -51,13 +29,519 @@ export interface File {
 | 
				
			|||||||
  fileId: string;
 | 
					  fileId: string;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const ChatWindow = () => {
 | 
					interface ChatModelProvider {
 | 
				
			||||||
  const { hasError, isReady, notFound, messages } = useChat();
 | 
					  name: string;
 | 
				
			||||||
 | 
					  provider: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface EmbeddingModelProvider {
 | 
				
			||||||
 | 
					  name: string;
 | 
				
			||||||
 | 
					  provider: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const checkConfig = async (
 | 
				
			||||||
 | 
					  setChatModelProvider: (provider: ChatModelProvider) => void,
 | 
				
			||||||
 | 
					  setEmbeddingModelProvider: (provider: EmbeddingModelProvider) => void,
 | 
				
			||||||
 | 
					  setIsConfigReady: (ready: boolean) => void,
 | 
				
			||||||
 | 
					  setHasError: (hasError: boolean) => void,
 | 
				
			||||||
 | 
					) => {
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    let chatModel = localStorage.getItem('chatModel');
 | 
				
			||||||
 | 
					    let chatModelProvider = localStorage.getItem('chatModelProvider');
 | 
				
			||||||
 | 
					    let embeddingModel = localStorage.getItem('embeddingModel');
 | 
				
			||||||
 | 
					    let embeddingModelProvider = localStorage.getItem('embeddingModelProvider');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const autoImageSearch = localStorage.getItem('autoImageSearch');
 | 
				
			||||||
 | 
					    const autoVideoSearch = localStorage.getItem('autoVideoSearch');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!autoImageSearch) {
 | 
				
			||||||
 | 
					      localStorage.setItem('autoImageSearch', 'true');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!autoVideoSearch) {
 | 
				
			||||||
 | 
					      localStorage.setItem('autoVideoSearch', 'false');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const providers = await fetch(`/api/models`, {
 | 
				
			||||||
 | 
					      headers: {
 | 
				
			||||||
 | 
					        'Content-Type': 'application/json',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    }).then(async (res) => {
 | 
				
			||||||
 | 
					      if (!res.ok)
 | 
				
			||||||
 | 
					        throw new Error(
 | 
				
			||||||
 | 
					          `Failed to fetch models: ${res.status} ${res.statusText}`,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					      return res.json();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (
 | 
				
			||||||
 | 
					      !chatModel ||
 | 
				
			||||||
 | 
					      !chatModelProvider ||
 | 
				
			||||||
 | 
					      !embeddingModel ||
 | 
				
			||||||
 | 
					      !embeddingModelProvider
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					      if (!chatModel || !chatModelProvider) {
 | 
				
			||||||
 | 
					        const chatModelProviders = providers.chatModelProviders;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        chatModelProvider =
 | 
				
			||||||
 | 
					          chatModelProvider || Object.keys(chatModelProviders)[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        chatModel = Object.keys(chatModelProviders[chatModelProvider])[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!chatModelProviders || Object.keys(chatModelProviders).length === 0)
 | 
				
			||||||
 | 
					          return toast.error('No chat models available');
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (!embeddingModel || !embeddingModelProvider) {
 | 
				
			||||||
 | 
					        const embeddingModelProviders = providers.embeddingModelProviders;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (
 | 
				
			||||||
 | 
					          !embeddingModelProviders ||
 | 
				
			||||||
 | 
					          Object.keys(embeddingModelProviders).length === 0
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					          return toast.error('No embedding models available');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        embeddingModelProvider = Object.keys(embeddingModelProviders)[0];
 | 
				
			||||||
 | 
					        embeddingModel = Object.keys(
 | 
				
			||||||
 | 
					          embeddingModelProviders[embeddingModelProvider],
 | 
				
			||||||
 | 
					        )[0];
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      localStorage.setItem('chatModel', chatModel!);
 | 
				
			||||||
 | 
					      localStorage.setItem('chatModelProvider', chatModelProvider);
 | 
				
			||||||
 | 
					      localStorage.setItem('embeddingModel', embeddingModel!);
 | 
				
			||||||
 | 
					      localStorage.setItem('embeddingModelProvider', embeddingModelProvider);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      const chatModelProviders = providers.chatModelProviders;
 | 
				
			||||||
 | 
					      const embeddingModelProviders = providers.embeddingModelProviders;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (
 | 
				
			||||||
 | 
					        Object.keys(chatModelProviders).length > 0 &&
 | 
				
			||||||
 | 
					        !chatModelProviders[chatModelProvider]
 | 
				
			||||||
 | 
					      ) {
 | 
				
			||||||
 | 
					        const chatModelProvidersKeys = Object.keys(chatModelProviders);
 | 
				
			||||||
 | 
					        chatModelProvider =
 | 
				
			||||||
 | 
					          chatModelProvidersKeys.find(
 | 
				
			||||||
 | 
					            (key) => Object.keys(chatModelProviders[key]).length > 0,
 | 
				
			||||||
 | 
					          ) || chatModelProvidersKeys[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        localStorage.setItem('chatModelProvider', chatModelProvider);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (
 | 
				
			||||||
 | 
					        chatModelProvider &&
 | 
				
			||||||
 | 
					        !chatModelProviders[chatModelProvider][chatModel]
 | 
				
			||||||
 | 
					      ) {
 | 
				
			||||||
 | 
					        chatModel = Object.keys(
 | 
				
			||||||
 | 
					          chatModelProviders[
 | 
				
			||||||
 | 
					            Object.keys(chatModelProviders[chatModelProvider]).length > 0
 | 
				
			||||||
 | 
					              ? chatModelProvider
 | 
				
			||||||
 | 
					              : Object.keys(chatModelProviders)[0]
 | 
				
			||||||
 | 
					          ],
 | 
				
			||||||
 | 
					        )[0];
 | 
				
			||||||
 | 
					        localStorage.setItem('chatModel', chatModel);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (
 | 
				
			||||||
 | 
					        Object.keys(embeddingModelProviders).length > 0 &&
 | 
				
			||||||
 | 
					        !embeddingModelProviders[embeddingModelProvider]
 | 
				
			||||||
 | 
					      ) {
 | 
				
			||||||
 | 
					        embeddingModelProvider = Object.keys(embeddingModelProviders)[0];
 | 
				
			||||||
 | 
					        localStorage.setItem('embeddingModelProvider', embeddingModelProvider);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (
 | 
				
			||||||
 | 
					        embeddingModelProvider &&
 | 
				
			||||||
 | 
					        !embeddingModelProviders[embeddingModelProvider][embeddingModel]
 | 
				
			||||||
 | 
					      ) {
 | 
				
			||||||
 | 
					        embeddingModel = Object.keys(
 | 
				
			||||||
 | 
					          embeddingModelProviders[embeddingModelProvider],
 | 
				
			||||||
 | 
					        )[0];
 | 
				
			||||||
 | 
					        localStorage.setItem('embeddingModel', embeddingModel);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setChatModelProvider({
 | 
				
			||||||
 | 
					      name: chatModel!,
 | 
				
			||||||
 | 
					      provider: chatModelProvider,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setEmbeddingModelProvider({
 | 
				
			||||||
 | 
					      name: embeddingModel!,
 | 
				
			||||||
 | 
					      provider: embeddingModelProvider,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setIsConfigReady(true);
 | 
				
			||||||
 | 
					  } catch (err) {
 | 
				
			||||||
 | 
					    console.error('An error occurred while checking the configuration:', err);
 | 
				
			||||||
 | 
					    setIsConfigReady(false);
 | 
				
			||||||
 | 
					    setHasError(true);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const loadMessages = async (
 | 
				
			||||||
 | 
					  chatId: string,
 | 
				
			||||||
 | 
					  setMessages: (messages: Message[]) => void,
 | 
				
			||||||
 | 
					  setIsMessagesLoaded: (loaded: boolean) => void,
 | 
				
			||||||
 | 
					  setChatHistory: (history: [string, string][]) => void,
 | 
				
			||||||
 | 
					  setFocusMode: (mode: string) => void,
 | 
				
			||||||
 | 
					  setNotFound: (notFound: boolean) => void,
 | 
				
			||||||
 | 
					  setFiles: (files: File[]) => void,
 | 
				
			||||||
 | 
					  setFileIds: (fileIds: string[]) => void,
 | 
				
			||||||
 | 
					) => {
 | 
				
			||||||
 | 
					  const res = await fetch(`/api/chats/${chatId}`, {
 | 
				
			||||||
 | 
					    method: 'GET',
 | 
				
			||||||
 | 
					    headers: {
 | 
				
			||||||
 | 
					      'Content-Type': 'application/json',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (res.status === 404) {
 | 
				
			||||||
 | 
					    setNotFound(true);
 | 
				
			||||||
 | 
					    setIsMessagesLoaded(true);
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const data = await res.json();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const messages = data.messages.map((msg: any) => {
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      ...msg,
 | 
				
			||||||
 | 
					      ...JSON.parse(msg.metadata),
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  }) as Message[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  setMessages(messages);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const history = messages.map((msg) => {
 | 
				
			||||||
 | 
					    return [msg.role, msg.content];
 | 
				
			||||||
 | 
					  }) as [string, string][];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  console.debug(new Date(), 'app:messages_loaded');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  document.title = messages[0].content;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const files = data.chat.files.map((file: any) => {
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      fileName: file.name,
 | 
				
			||||||
 | 
					      fileExtension: file.name.split('.').pop(),
 | 
				
			||||||
 | 
					      fileId: file.fileId,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  setFiles(files);
 | 
				
			||||||
 | 
					  setFileIds(files.map((file: File) => file.fileId));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  setChatHistory(history);
 | 
				
			||||||
 | 
					  setFocusMode(data.chat.focusMode);
 | 
				
			||||||
 | 
					  setIsMessagesLoaded(true);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ChatWindow = ({ id }: { id?: string }) => {
 | 
				
			||||||
 | 
					  const searchParams = useSearchParams();
 | 
				
			||||||
 | 
					  const initialMessage = searchParams.get('q');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const [chatId, setChatId] = useState<string | undefined>(id);
 | 
				
			||||||
 | 
					  const [newChatCreated, setNewChatCreated] = useState(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const [chatModelProvider, setChatModelProvider] = useState<ChatModelProvider>(
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      name: '',
 | 
				
			||||||
 | 
					      provider: '',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const [embeddingModelProvider, setEmbeddingModelProvider] =
 | 
				
			||||||
 | 
					    useState<EmbeddingModelProvider>({
 | 
				
			||||||
 | 
					      name: '',
 | 
				
			||||||
 | 
					      provider: '',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const [isConfigReady, setIsConfigReady] = useState(false);
 | 
				
			||||||
 | 
					  const [hasError, setHasError] = useState(false);
 | 
				
			||||||
 | 
					  const [isReady, setIsReady] = useState(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    checkConfig(
 | 
				
			||||||
 | 
					      setChatModelProvider,
 | 
				
			||||||
 | 
					      setEmbeddingModelProvider,
 | 
				
			||||||
 | 
					      setIsConfigReady,
 | 
				
			||||||
 | 
					      setHasError,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    // eslint-disable-next-line react-hooks/exhaustive-deps
 | 
				
			||||||
 | 
					  }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const [loading, setLoading] = useState(false);
 | 
				
			||||||
 | 
					  const [messageAppeared, setMessageAppeared] = useState(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const [chatHistory, setChatHistory] = useState<[string, string][]>([]);
 | 
				
			||||||
 | 
					  const [messages, setMessages] = useState<Message[]>([]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const [files, setFiles] = useState<File[]>([]);
 | 
				
			||||||
 | 
					  const [fileIds, setFileIds] = useState<string[]>([]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const [focusMode, setFocusMode] = useState('webSearch');
 | 
				
			||||||
 | 
					  const [optimizationMode, setOptimizationMode] = useState('speed');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const [isMessagesLoaded, setIsMessagesLoaded] = useState(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const [notFound, setNotFound] = useState(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    if (
 | 
				
			||||||
 | 
					      chatId &&
 | 
				
			||||||
 | 
					      !newChatCreated &&
 | 
				
			||||||
 | 
					      !isMessagesLoaded &&
 | 
				
			||||||
 | 
					      messages.length === 0
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					      loadMessages(
 | 
				
			||||||
 | 
					        chatId,
 | 
				
			||||||
 | 
					        setMessages,
 | 
				
			||||||
 | 
					        setIsMessagesLoaded,
 | 
				
			||||||
 | 
					        setChatHistory,
 | 
				
			||||||
 | 
					        setFocusMode,
 | 
				
			||||||
 | 
					        setNotFound,
 | 
				
			||||||
 | 
					        setFiles,
 | 
				
			||||||
 | 
					        setFileIds,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    } else if (!chatId) {
 | 
				
			||||||
 | 
					      setNewChatCreated(true);
 | 
				
			||||||
 | 
					      setIsMessagesLoaded(true);
 | 
				
			||||||
 | 
					      setChatId(crypto.randomBytes(20).toString('hex'));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    // eslint-disable-next-line react-hooks/exhaustive-deps
 | 
				
			||||||
 | 
					  }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const messagesRef = useRef<Message[]>([]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    messagesRef.current = messages;
 | 
				
			||||||
 | 
					  }, [messages]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    if (isMessagesLoaded && isConfigReady) {
 | 
				
			||||||
 | 
					      setIsReady(true);
 | 
				
			||||||
 | 
					      console.debug(new Date(), 'app:ready');
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      setIsReady(false);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }, [isMessagesLoaded, isConfigReady]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const sendMessage = async (message: string, messageId?: string) => {
 | 
				
			||||||
 | 
					    if (loading) return;
 | 
				
			||||||
 | 
					    if (!isConfigReady) {
 | 
				
			||||||
 | 
					      toast.error('Cannot send message before the configuration is ready');
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setLoading(true);
 | 
				
			||||||
 | 
					    setMessageAppeared(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let sources: Document[] | undefined = undefined;
 | 
				
			||||||
 | 
					    let recievedMessage = '';
 | 
				
			||||||
 | 
					    let added = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    messageId = messageId ?? crypto.randomBytes(7).toString('hex');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setMessages((prevMessages) => [
 | 
				
			||||||
 | 
					      ...prevMessages,
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        content: message,
 | 
				
			||||||
 | 
					        messageId: messageId,
 | 
				
			||||||
 | 
					        chatId: chatId!,
 | 
				
			||||||
 | 
					        role: 'user',
 | 
				
			||||||
 | 
					        createdAt: new Date(),
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const messageHandler = async (data: any) => {
 | 
				
			||||||
 | 
					      if (data.type === 'error') {
 | 
				
			||||||
 | 
					        toast.error(data.data);
 | 
				
			||||||
 | 
					        setLoading(false);
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (data.type === 'sources') {
 | 
				
			||||||
 | 
					        sources = data.data;
 | 
				
			||||||
 | 
					        if (!added) {
 | 
				
			||||||
 | 
					          setMessages((prevMessages) => [
 | 
				
			||||||
 | 
					            ...prevMessages,
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					              content: '',
 | 
				
			||||||
 | 
					              messageId: data.messageId,
 | 
				
			||||||
 | 
					              chatId: chatId!,
 | 
				
			||||||
 | 
					              role: 'assistant',
 | 
				
			||||||
 | 
					              sources: sources,
 | 
				
			||||||
 | 
					              createdAt: new Date(),
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          ]);
 | 
				
			||||||
 | 
					          added = true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        setMessageAppeared(true);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (data.type === 'message') {
 | 
				
			||||||
 | 
					        if (!added) {
 | 
				
			||||||
 | 
					          setMessages((prevMessages) => [
 | 
				
			||||||
 | 
					            ...prevMessages,
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					              content: data.data,
 | 
				
			||||||
 | 
					              messageId: data.messageId,
 | 
				
			||||||
 | 
					              chatId: chatId!,
 | 
				
			||||||
 | 
					              role: 'assistant',
 | 
				
			||||||
 | 
					              sources: sources,
 | 
				
			||||||
 | 
					              createdAt: new Date(),
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          ]);
 | 
				
			||||||
 | 
					          added = true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        setMessages((prev) =>
 | 
				
			||||||
 | 
					          prev.map((message) => {
 | 
				
			||||||
 | 
					            if (message.messageId === data.messageId) {
 | 
				
			||||||
 | 
					              return { ...message, content: message.content + data.data };
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return message;
 | 
				
			||||||
 | 
					          }),
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        recievedMessage += data.data;
 | 
				
			||||||
 | 
					        setMessageAppeared(true);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (data.type === 'messageEnd') {
 | 
				
			||||||
 | 
					        setChatHistory((prevHistory) => [
 | 
				
			||||||
 | 
					          ...prevHistory,
 | 
				
			||||||
 | 
					          ['human', message],
 | 
				
			||||||
 | 
					          ['assistant', recievedMessage],
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        setLoading(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const lastMsg = messagesRef.current[messagesRef.current.length - 1];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const autoImageSearch = localStorage.getItem('autoImageSearch');
 | 
				
			||||||
 | 
					        const autoVideoSearch = localStorage.getItem('autoVideoSearch');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (autoImageSearch === 'true') {
 | 
				
			||||||
 | 
					          document
 | 
				
			||||||
 | 
					            .getElementById(`search-images-${lastMsg.messageId}`)
 | 
				
			||||||
 | 
					            ?.click();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (autoVideoSearch === 'true') {
 | 
				
			||||||
 | 
					          document
 | 
				
			||||||
 | 
					            .getElementById(`search-videos-${lastMsg.messageId}`)
 | 
				
			||||||
 | 
					            ?.click();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (
 | 
				
			||||||
 | 
					          lastMsg.role === 'assistant' &&
 | 
				
			||||||
 | 
					          lastMsg.sources &&
 | 
				
			||||||
 | 
					          lastMsg.sources.length > 0 &&
 | 
				
			||||||
 | 
					          !lastMsg.suggestions
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
 | 
					          const suggestions = await getSuggestions(messagesRef.current);
 | 
				
			||||||
 | 
					          setMessages((prev) =>
 | 
				
			||||||
 | 
					            prev.map((msg) => {
 | 
				
			||||||
 | 
					              if (msg.messageId === lastMsg.messageId) {
 | 
				
			||||||
 | 
					                return { ...msg, suggestions: suggestions };
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					              return msg;
 | 
				
			||||||
 | 
					            }),
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const res = await fetch('/api/chat', {
 | 
				
			||||||
 | 
					      method: 'POST',
 | 
				
			||||||
 | 
					      headers: {
 | 
				
			||||||
 | 
					        'Content-Type': 'application/json',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      body: JSON.stringify({
 | 
				
			||||||
 | 
					        content: message,
 | 
				
			||||||
 | 
					        message: {
 | 
				
			||||||
 | 
					          messageId: messageId,
 | 
				
			||||||
 | 
					          chatId: chatId!,
 | 
				
			||||||
 | 
					          content: message,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        chatId: chatId!,
 | 
				
			||||||
 | 
					        files: fileIds,
 | 
				
			||||||
 | 
					        focusMode: focusMode,
 | 
				
			||||||
 | 
					        optimizationMode: optimizationMode,
 | 
				
			||||||
 | 
					        history: chatHistory,
 | 
				
			||||||
 | 
					        chatModel: {
 | 
				
			||||||
 | 
					          name: chatModelProvider.name,
 | 
				
			||||||
 | 
					          provider: chatModelProvider.provider,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        embeddingModel: {
 | 
				
			||||||
 | 
					          name: embeddingModelProvider.name,
 | 
				
			||||||
 | 
					          provider: embeddingModelProvider.provider,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        systemInstructions: localStorage.getItem('systemInstructions'),
 | 
				
			||||||
 | 
					      }),
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!res.body) throw new Error('No response body');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const reader = res.body?.getReader();
 | 
				
			||||||
 | 
					    const decoder = new TextDecoder('utf-8');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let partialChunk = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    while (true) {
 | 
				
			||||||
 | 
					      const { value, done } = await reader.read();
 | 
				
			||||||
 | 
					      if (done) break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      partialChunk += decoder.decode(value, { stream: true });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        const messages = partialChunk.split('\n');
 | 
				
			||||||
 | 
					        for (const msg of messages) {
 | 
				
			||||||
 | 
					          if (!msg.trim()) continue;
 | 
				
			||||||
 | 
					          const json = JSON.parse(msg);
 | 
				
			||||||
 | 
					          messageHandler(json);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        partialChunk = '';
 | 
				
			||||||
 | 
					      } catch (error) {
 | 
				
			||||||
 | 
					        console.warn('Incomplete JSON, waiting for next chunk...');
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const rewrite = (messageId: string) => {
 | 
				
			||||||
 | 
					    const index = messages.findIndex((msg) => msg.messageId === messageId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (index === -1) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const message = messages[index - 1];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setMessages((prev) => {
 | 
				
			||||||
 | 
					      return [...prev.slice(0, messages.length > 2 ? index - 1 : 0)];
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    setChatHistory((prev) => {
 | 
				
			||||||
 | 
					      return [...prev.slice(0, messages.length > 2 ? index - 1 : 0)];
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    sendMessage(message.content, message.messageId);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    if (isReady && initialMessage && isConfigReady) {
 | 
				
			||||||
 | 
					      sendMessage(initialMessage);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    // eslint-disable-next-line react-hooks/exhaustive-deps
 | 
				
			||||||
 | 
					  }, [isConfigReady, isReady, initialMessage]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (hasError) {
 | 
					  if (hasError) {
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <div className="relative">
 | 
					      <div className="relative">
 | 
				
			||||||
        <div className="absolute w-full flex flex-row items-center justify-end mr-5 mt-5">
 | 
					        <div className="absolute w-full flex flex-row items-center justify-end mr-5 mt-5">
 | 
				
			||||||
          <SettingsButtonMobile />
 | 
					          <Link href="/settings">
 | 
				
			||||||
 | 
					            <Settings className="cursor-pointer lg:hidden" />
 | 
				
			||||||
 | 
					          </Link>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <div className="flex flex-col items-center justify-center min-h-screen">
 | 
					        <div className="flex flex-col items-center justify-center min-h-screen">
 | 
				
			||||||
          <p className="dark:text-white/70 text-black/70 text-sm">
 | 
					          <p className="dark:text-white/70 text-black/70 text-sm">
 | 
				
			||||||
@@ -75,17 +559,52 @@ const ChatWindow = () => {
 | 
				
			|||||||
      <div>
 | 
					      <div>
 | 
				
			||||||
        {messages.length > 0 ? (
 | 
					        {messages.length > 0 ? (
 | 
				
			||||||
          <>
 | 
					          <>
 | 
				
			||||||
            <Navbar />
 | 
					            <Navbar chatId={chatId!} messages={messages} />
 | 
				
			||||||
            <Chat />
 | 
					            <Chat
 | 
				
			||||||
 | 
					              loading={loading}
 | 
				
			||||||
 | 
					              messages={messages}
 | 
				
			||||||
 | 
					              sendMessage={sendMessage}
 | 
				
			||||||
 | 
					              messageAppeared={messageAppeared}
 | 
				
			||||||
 | 
					              rewrite={rewrite}
 | 
				
			||||||
 | 
					              fileIds={fileIds}
 | 
				
			||||||
 | 
					              setFileIds={setFileIds}
 | 
				
			||||||
 | 
					              files={files}
 | 
				
			||||||
 | 
					              setFiles={setFiles}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
          </>
 | 
					          </>
 | 
				
			||||||
        ) : (
 | 
					        ) : (
 | 
				
			||||||
          <EmptyChat />
 | 
					          <EmptyChat
 | 
				
			||||||
 | 
					            sendMessage={sendMessage}
 | 
				
			||||||
 | 
					            focusMode={focusMode}
 | 
				
			||||||
 | 
					            setFocusMode={setFocusMode}
 | 
				
			||||||
 | 
					            optimizationMode={optimizationMode}
 | 
				
			||||||
 | 
					            setOptimizationMode={setOptimizationMode}
 | 
				
			||||||
 | 
					            fileIds={fileIds}
 | 
				
			||||||
 | 
					            setFileIds={setFileIds}
 | 
				
			||||||
 | 
					            files={files}
 | 
				
			||||||
 | 
					            setFiles={setFiles}
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
        )}
 | 
					        )}
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
  ) : (
 | 
					  ) : (
 | 
				
			||||||
    <div className="flex flex-row items-center justify-center min-h-screen">
 | 
					    <div className="flex flex-row items-center justify-center min-h-screen">
 | 
				
			||||||
      <Loader />
 | 
					      <svg
 | 
				
			||||||
 | 
					        aria-hidden="true"
 | 
				
			||||||
 | 
					        className="w-8 h-8 text-light-200 fill-light-secondary dark:text-[#202020] animate-spin dark:fill-[#ffffff3b]"
 | 
				
			||||||
 | 
					        viewBox="0 0 100 101"
 | 
				
			||||||
 | 
					        fill="none"
 | 
				
			||||||
 | 
					        xmlns="http://www.w3.org/2000/svg"
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        <path
 | 
				
			||||||
 | 
					          d="M100 50.5908C100.003 78.2051 78.1951 100.003 50.5908 100C22.9765 99.9972 0.997224 78.018 1 50.4037C1.00281 22.7993 22.8108 0.997224 50.4251 1C78.0395 1.00281 100.018 22.8108 100 50.4251ZM9.08164 50.594C9.06312 73.3997 27.7909 92.1272 50.5966 92.1457C73.4023 92.1642 92.1298 73.4365 92.1483 50.6308C92.1669 27.8251 73.4392 9.0973 50.6335 9.07878C27.8278 9.06026 9.10003 27.787 9.08164 50.594Z"
 | 
				
			||||||
 | 
					          fill="currentColor"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					        <path
 | 
				
			||||||
 | 
					          d="M93.9676 39.0409C96.393 38.4037 97.8624 35.9116 96.9801 33.5533C95.1945 28.8227 92.871 24.3692 90.0681 20.348C85.6237 14.1775 79.4473 9.36872 72.0454 6.45794C64.6435 3.54717 56.3134 2.65431 48.3133 3.89319C45.869 4.27179 44.3768 6.77534 45.014 9.20079C45.6512 11.6262 48.1343 13.0956 50.5786 12.717C56.5073 11.8281 62.5542 12.5399 68.0406 14.7911C73.527 17.0422 78.2187 20.7487 81.5841 25.4923C83.7976 28.5886 85.4467 32.059 86.4416 35.7474C87.1273 38.1189 89.5423 39.6781 91.9676 39.0409Z"
 | 
				
			||||||
 | 
					          fill="currentFill"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      </svg>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,19 +0,0 @@
 | 
				
			|||||||
const Citation = ({
 | 
					 | 
				
			||||||
  href,
 | 
					 | 
				
			||||||
  children,
 | 
					 | 
				
			||||||
}: {
 | 
					 | 
				
			||||||
  href: string;
 | 
					 | 
				
			||||||
  children: React.ReactNode;
 | 
					 | 
				
			||||||
}) => {
 | 
					 | 
				
			||||||
  return (
 | 
					 | 
				
			||||||
    <a
 | 
					 | 
				
			||||||
      href={href}
 | 
					 | 
				
			||||||
      target="_blank"
 | 
					 | 
				
			||||||
      className="bg-light-secondary dark:bg-dark-secondary px-1 rounded ml-1 no-underline text-xs text-black/70 dark:text-white/70 relative"
 | 
					 | 
				
			||||||
    >
 | 
					 | 
				
			||||||
      {children}
 | 
					 | 
				
			||||||
    </a>
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default Citation;
 | 
					 | 
				
			||||||
@@ -1,70 +0,0 @@
 | 
				
			|||||||
import { Discover } from '@/app/discover/page';
 | 
					 | 
				
			||||||
import Link from 'next/link';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const MajorNewsCard = ({
 | 
					 | 
				
			||||||
  item,
 | 
					 | 
				
			||||||
  isLeft = true,
 | 
					 | 
				
			||||||
}: {
 | 
					 | 
				
			||||||
  item: Discover;
 | 
					 | 
				
			||||||
  isLeft?: boolean;
 | 
					 | 
				
			||||||
}) => (
 | 
					 | 
				
			||||||
  <Link
 | 
					 | 
				
			||||||
    href={`/?q=Summary: ${item.url}`}
 | 
					 | 
				
			||||||
    className="w-full group flex flex-row items-stretch gap-6 h-60 py-3"
 | 
					 | 
				
			||||||
    target="_blank"
 | 
					 | 
				
			||||||
  >
 | 
					 | 
				
			||||||
    {isLeft ? (
 | 
					 | 
				
			||||||
      <>
 | 
					 | 
				
			||||||
        <div className="relative w-80 h-full overflow-hidden rounded-2xl flex-shrink-0">
 | 
					 | 
				
			||||||
          <img
 | 
					 | 
				
			||||||
            className="object-cover w-full h-full group-hover:scale-105 transition-transform duration-500"
 | 
					 | 
				
			||||||
            src={
 | 
					 | 
				
			||||||
              new URL(item.thumbnail).origin +
 | 
					 | 
				
			||||||
              new URL(item.thumbnail).pathname +
 | 
					 | 
				
			||||||
              `?id=${new URL(item.thumbnail).searchParams.get('id')}`
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            alt={item.title}
 | 
					 | 
				
			||||||
          />
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
        <div className="flex flex-col justify-center flex-1 py-4">
 | 
					 | 
				
			||||||
          <h2
 | 
					 | 
				
			||||||
            className="text-3xl font-light mb-3 leading-tight line-clamp-3 group-hover:text-cyan-500 dark:group-hover:text-cyan-300 transition duration-200"
 | 
					 | 
				
			||||||
            style={{ fontFamily: 'PP Editorial, serif' }}
 | 
					 | 
				
			||||||
          >
 | 
					 | 
				
			||||||
            {item.title}
 | 
					 | 
				
			||||||
          </h2>
 | 
					 | 
				
			||||||
          <p className="text-black/60 dark:text-white/60 text-base leading-relaxed line-clamp-4">
 | 
					 | 
				
			||||||
            {item.content}
 | 
					 | 
				
			||||||
          </p>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
      </>
 | 
					 | 
				
			||||||
    ) : (
 | 
					 | 
				
			||||||
      <>
 | 
					 | 
				
			||||||
        <div className="flex flex-col justify-center flex-1 py-4">
 | 
					 | 
				
			||||||
          <h2
 | 
					 | 
				
			||||||
            className="text-3xl font-light mb-3 leading-tight line-clamp-3 group-hover:text-cyan-500 dark:group-hover:text-cyan-300 transition duration-200"
 | 
					 | 
				
			||||||
            style={{ fontFamily: 'PP Editorial, serif' }}
 | 
					 | 
				
			||||||
          >
 | 
					 | 
				
			||||||
            {item.title}
 | 
					 | 
				
			||||||
          </h2>
 | 
					 | 
				
			||||||
          <p className="text-black/60 dark:text-white/60 text-base leading-relaxed line-clamp-4">
 | 
					 | 
				
			||||||
            {item.content}
 | 
					 | 
				
			||||||
          </p>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
        <div className="relative w-80 h-full overflow-hidden rounded-2xl flex-shrink-0">
 | 
					 | 
				
			||||||
          <img
 | 
					 | 
				
			||||||
            className="object-cover w-full h-full group-hover:scale-105 transition-transform duration-500"
 | 
					 | 
				
			||||||
            src={
 | 
					 | 
				
			||||||
              new URL(item.thumbnail).origin +
 | 
					 | 
				
			||||||
              new URL(item.thumbnail).pathname +
 | 
					 | 
				
			||||||
              `?id=${new URL(item.thumbnail).searchParams.get('id')}`
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            alt={item.title}
 | 
					 | 
				
			||||||
          />
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
      </>
 | 
					 | 
				
			||||||
    )}
 | 
					 | 
				
			||||||
  </Link>
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default MajorNewsCard;
 | 
					 | 
				
			||||||
@@ -1,32 +0,0 @@
 | 
				
			|||||||
import { Discover } from '@/app/discover/page';
 | 
					 | 
				
			||||||
import Link from 'next/link';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const SmallNewsCard = ({ item }: { item: Discover }) => (
 | 
					 | 
				
			||||||
  <Link
 | 
					 | 
				
			||||||
    href={`/?q=Summary: ${item.url}`}
 | 
					 | 
				
			||||||
    className="rounded-3xl overflow-hidden bg-light-secondary dark:bg-dark-secondary shadow-sm shadow-light-200/10 dark:shadow-black/25 group flex flex-col"
 | 
					 | 
				
			||||||
    target="_blank"
 | 
					 | 
				
			||||||
  >
 | 
					 | 
				
			||||||
    <div className="relative aspect-video overflow-hidden">
 | 
					 | 
				
			||||||
      <img
 | 
					 | 
				
			||||||
        className="object-cover w-full h-full group-hover:scale-105 transition-transform duration-300"
 | 
					 | 
				
			||||||
        src={
 | 
					 | 
				
			||||||
          new URL(item.thumbnail).origin +
 | 
					 | 
				
			||||||
          new URL(item.thumbnail).pathname +
 | 
					 | 
				
			||||||
          `?id=${new URL(item.thumbnail).searchParams.get('id')}`
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        alt={item.title}
 | 
					 | 
				
			||||||
      />
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
    <div className="p-4">
 | 
					 | 
				
			||||||
      <h3 className="font-semibold text-sm mb-2 leading-tight line-clamp-2 group-hover:text-cyan-500 dark:group-hover:text-cyan-300 transition duration-200">
 | 
					 | 
				
			||||||
        {item.title}
 | 
					 | 
				
			||||||
      </h3>
 | 
					 | 
				
			||||||
      <p className="text-black/60 dark:text-white/60 text-xs leading-relaxed line-clamp-2">
 | 
					 | 
				
			||||||
        {item.content}
 | 
					 | 
				
			||||||
      </p>
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
  </Link>
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default SmallNewsCard;
 | 
					 | 
				
			||||||
@@ -1,32 +1,54 @@
 | 
				
			|||||||
import { Settings } from 'lucide-react';
 | 
					import { Settings } from 'lucide-react';
 | 
				
			||||||
import EmptyChatMessageInput from './EmptyChatMessageInput';
 | 
					import EmptyChatMessageInput from './EmptyChatMessageInput';
 | 
				
			||||||
 | 
					import { useState } from 'react';
 | 
				
			||||||
import { File } from './ChatWindow';
 | 
					import { File } from './ChatWindow';
 | 
				
			||||||
import Link from 'next/link';
 | 
					import Link from 'next/link';
 | 
				
			||||||
import WeatherWidget from './WeatherWidget';
 | 
					 | 
				
			||||||
import NewsArticleWidget from './NewsArticleWidget';
 | 
					 | 
				
			||||||
import SettingsButtonMobile from '@/components/Settings/SettingsButtonMobile';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const EmptyChat = () => {
 | 
					const EmptyChat = ({
 | 
				
			||||||
 | 
					  sendMessage,
 | 
				
			||||||
 | 
					  focusMode,
 | 
				
			||||||
 | 
					  setFocusMode,
 | 
				
			||||||
 | 
					  optimizationMode,
 | 
				
			||||||
 | 
					  setOptimizationMode,
 | 
				
			||||||
 | 
					  fileIds,
 | 
				
			||||||
 | 
					  setFileIds,
 | 
				
			||||||
 | 
					  files,
 | 
				
			||||||
 | 
					  setFiles,
 | 
				
			||||||
 | 
					}: {
 | 
				
			||||||
 | 
					  sendMessage: (message: string) => void;
 | 
				
			||||||
 | 
					  focusMode: string;
 | 
				
			||||||
 | 
					  setFocusMode: (mode: string) => void;
 | 
				
			||||||
 | 
					  optimizationMode: string;
 | 
				
			||||||
 | 
					  setOptimizationMode: (mode: string) => void;
 | 
				
			||||||
 | 
					  fileIds: string[];
 | 
				
			||||||
 | 
					  setFileIds: (fileIds: string[]) => void;
 | 
				
			||||||
 | 
					  files: File[];
 | 
				
			||||||
 | 
					  setFiles: (files: File[]) => void;
 | 
				
			||||||
 | 
					}) => {
 | 
				
			||||||
 | 
					  const [isSettingsOpen, setIsSettingsOpen] = useState(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <div className="relative">
 | 
					    <div className="relative">
 | 
				
			||||||
      <div className="absolute w-full flex flex-row items-center justify-end mr-5 mt-5">
 | 
					      <div className="absolute w-full flex flex-row items-center justify-end mr-5 mt-5">
 | 
				
			||||||
        <SettingsButtonMobile />
 | 
					        <Link href="/settings">
 | 
				
			||||||
 | 
					          <Settings className="cursor-pointer lg:hidden" />
 | 
				
			||||||
 | 
					        </Link>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <div className="flex flex-col items-center justify-center min-h-screen max-w-screen-sm mx-auto p-2 space-y-4">
 | 
					      <div className="flex flex-col items-center justify-center min-h-screen max-w-screen-sm mx-auto p-2 space-y-8">
 | 
				
			||||||
        <div className="flex flex-col items-center justify-center w-full space-y-8">
 | 
					        <h2 className="text-black/70 dark:text-white/70 text-3xl font-medium -mt-8">
 | 
				
			||||||
          <h2 className="text-black/70 dark:text-white/70 text-3xl font-medium -mt-8">
 | 
					          Research begins here.
 | 
				
			||||||
            Research begins here.
 | 
					        </h2>
 | 
				
			||||||
          </h2>
 | 
					        <EmptyChatMessageInput
 | 
				
			||||||
          <EmptyChatMessageInput />
 | 
					          sendMessage={sendMessage}
 | 
				
			||||||
        </div>
 | 
					          focusMode={focusMode}
 | 
				
			||||||
        <div className="flex flex-col w-full gap-4 mt-2 sm:flex-row sm:justify-center">
 | 
					          setFocusMode={setFocusMode}
 | 
				
			||||||
          <div className="flex-1 w-full">
 | 
					          optimizationMode={optimizationMode}
 | 
				
			||||||
            <WeatherWidget />
 | 
					          setOptimizationMode={setOptimizationMode}
 | 
				
			||||||
          </div>
 | 
					          fileIds={fileIds}
 | 
				
			||||||
          <div className="flex-1 w-full">
 | 
					          setFileIds={setFileIds}
 | 
				
			||||||
            <NewsArticleWidget />
 | 
					          files={files}
 | 
				
			||||||
          </div>
 | 
					          setFiles={setFiles}
 | 
				
			||||||
        </div>
 | 
					        />
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,16 +1,34 @@
 | 
				
			|||||||
import { ArrowRight } from 'lucide-react';
 | 
					import { ArrowRight } from 'lucide-react';
 | 
				
			||||||
import { useEffect, useRef, useState } from 'react';
 | 
					import { useEffect, useRef, useState } from 'react';
 | 
				
			||||||
import TextareaAutosize from 'react-textarea-autosize';
 | 
					import TextareaAutosize from 'react-textarea-autosize';
 | 
				
			||||||
 | 
					import CopilotToggle from './MessageInputActions/Copilot';
 | 
				
			||||||
import Focus from './MessageInputActions/Focus';
 | 
					import Focus from './MessageInputActions/Focus';
 | 
				
			||||||
import Optimization from './MessageInputActions/Optimization';
 | 
					import Optimization from './MessageInputActions/Optimization';
 | 
				
			||||||
import Attach from './MessageInputActions/Attach';
 | 
					import Attach from './MessageInputActions/Attach';
 | 
				
			||||||
import { useChat } from '@/lib/hooks/useChat';
 | 
					import { File } from './ChatWindow';
 | 
				
			||||||
import ModelSelector from './MessageInputActions/ChatModelSelector';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const EmptyChatMessageInput = () => {
 | 
					const EmptyChatMessageInput = ({
 | 
				
			||||||
  const { sendMessage } = useChat();
 | 
					  sendMessage,
 | 
				
			||||||
 | 
					  focusMode,
 | 
				
			||||||
  /* const [copilotEnabled, setCopilotEnabled] = useState(false); */
 | 
					  setFocusMode,
 | 
				
			||||||
 | 
					  optimizationMode,
 | 
				
			||||||
 | 
					  setOptimizationMode,
 | 
				
			||||||
 | 
					  fileIds,
 | 
				
			||||||
 | 
					  setFileIds,
 | 
				
			||||||
 | 
					  files,
 | 
				
			||||||
 | 
					  setFiles,
 | 
				
			||||||
 | 
					}: {
 | 
				
			||||||
 | 
					  sendMessage: (message: string) => void;
 | 
				
			||||||
 | 
					  focusMode: string;
 | 
				
			||||||
 | 
					  setFocusMode: (mode: string) => void;
 | 
				
			||||||
 | 
					  optimizationMode: string;
 | 
				
			||||||
 | 
					  setOptimizationMode: (mode: string) => void;
 | 
				
			||||||
 | 
					  fileIds: string[];
 | 
				
			||||||
 | 
					  setFileIds: (fileIds: string[]) => void;
 | 
				
			||||||
 | 
					  files: File[];
 | 
				
			||||||
 | 
					  setFiles: (files: File[]) => void;
 | 
				
			||||||
 | 
					}) => {
 | 
				
			||||||
 | 
					  const [copilotEnabled, setCopilotEnabled] = useState(false);
 | 
				
			||||||
  const [message, setMessage] = useState('');
 | 
					  const [message, setMessage] = useState('');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const inputRef = useRef<HTMLTextAreaElement | null>(null);
 | 
					  const inputRef = useRef<HTMLTextAreaElement | null>(null);
 | 
				
			||||||
@@ -55,26 +73,34 @@ const EmptyChatMessageInput = () => {
 | 
				
			|||||||
      }}
 | 
					      }}
 | 
				
			||||||
      className="w-full"
 | 
					      className="w-full"
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
      <div className="flex flex-col bg-light-secondary dark:bg-dark-secondary px-3 pt-5 pb-3 rounded-2xl w-full border border-light-200 dark:border-dark-200 shadow-sm shadow-light-200/10 dark:shadow-black/20 transition-all duration-200 focus-within:border-light-300 dark:focus-within:border-dark-300">
 | 
					      <div className="flex flex-col bg-light-secondary dark:bg-dark-secondary px-5 pt-5 pb-2 rounded-lg w-full border border-light-200 dark:border-dark-200">
 | 
				
			||||||
        <TextareaAutosize
 | 
					        <TextareaAutosize
 | 
				
			||||||
          ref={inputRef}
 | 
					          ref={inputRef}
 | 
				
			||||||
          value={message}
 | 
					          value={message}
 | 
				
			||||||
          onChange={(e) => setMessage(e.target.value)}
 | 
					          onChange={(e) => setMessage(e.target.value)}
 | 
				
			||||||
          minRows={2}
 | 
					          minRows={2}
 | 
				
			||||||
          className="px-2 bg-transparent placeholder:text-[15px] placeholder:text-black/50 dark:placeholder:text-white/50 text-sm text-black dark:text-white resize-none focus:outline-none w-full max-h-24 lg:max-h-36 xl:max-h-48"
 | 
					          className="bg-transparent placeholder:text-black/50 dark:placeholder:text-white/50 text-sm text-black dark:text-white resize-none focus:outline-none w-full max-h-24 lg:max-h-36 xl:max-h-48"
 | 
				
			||||||
          placeholder="Ask anything..."
 | 
					          placeholder="Ask anything..."
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
        <div className="flex flex-row items-center justify-between mt-4">
 | 
					        <div className="flex flex-row items-center justify-between mt-4">
 | 
				
			||||||
          <Optimization />
 | 
					          <div className="flex flex-row items-center space-x-2 lg:space-x-4">
 | 
				
			||||||
          <div className="flex flex-row items-center space-x-2">
 | 
					            <Focus focusMode={focusMode} setFocusMode={setFocusMode} />
 | 
				
			||||||
            <div className="flex flex-row items-center space-x-1">
 | 
					            <Attach
 | 
				
			||||||
              <ModelSelector />
 | 
					              fileIds={fileIds}
 | 
				
			||||||
              <Focus />
 | 
					              setFileIds={setFileIds}
 | 
				
			||||||
              <Attach />
 | 
					              files={files}
 | 
				
			||||||
            </div>
 | 
					              setFiles={setFiles}
 | 
				
			||||||
 | 
					              showText
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					          <div className="flex flex-row items-center space-x-1 sm:space-x-4">
 | 
				
			||||||
 | 
					            <Optimization
 | 
				
			||||||
 | 
					              optimizationMode={optimizationMode}
 | 
				
			||||||
 | 
					              setOptimizationMode={setOptimizationMode}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
            <button
 | 
					            <button
 | 
				
			||||||
              disabled={message.trim().length === 0}
 | 
					              disabled={message.trim().length === 0}
 | 
				
			||||||
              className="bg-sky-500 text-white disabled:text-black/50 dark:disabled:text-white/50 disabled:bg-[#e0e0dc] dark:disabled:bg-[#ececec21] hover:bg-opacity-85 transition duration-100 rounded-full p-2"
 | 
					              className="bg-[#24A0ED] text-white disabled:text-black/50 dark:disabled:text-white/50 disabled:bg-[#e0e0dc] dark:disabled:bg-[#ececec21] hover:bg-opacity-85 transition duration-100 rounded-full p-2"
 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
              <ArrowRight className="bg-background" size={17} />
 | 
					              <ArrowRight className="bg-background" size={17} />
 | 
				
			||||||
            </button>
 | 
					            </button>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,13 +1,12 @@
 | 
				
			|||||||
import { Check, ClipboardList } from 'lucide-react';
 | 
					import { Check, ClipboardList } from 'lucide-react';
 | 
				
			||||||
import { Message } from '../ChatWindow';
 | 
					import { Message } from '../ChatWindow';
 | 
				
			||||||
import { useState } from 'react';
 | 
					import { useState } from 'react';
 | 
				
			||||||
import { Section } from '@/lib/hooks/useChat';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Copy = ({
 | 
					const Copy = ({
 | 
				
			||||||
  section,
 | 
					  message,
 | 
				
			||||||
  initialMessage,
 | 
					  initialMessage,
 | 
				
			||||||
}: {
 | 
					}: {
 | 
				
			||||||
  section: Section;
 | 
					  message: Message;
 | 
				
			||||||
  initialMessage: string;
 | 
					  initialMessage: string;
 | 
				
			||||||
}) => {
 | 
					}) => {
 | 
				
			||||||
  const [copied, setCopied] = useState(false);
 | 
					  const [copied, setCopied] = useState(false);
 | 
				
			||||||
@@ -15,7 +14,7 @@ const Copy = ({
 | 
				
			|||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <button
 | 
					    <button
 | 
				
			||||||
      onClick={() => {
 | 
					      onClick={() => {
 | 
				
			||||||
        const contentToCopy = `${initialMessage}${section?.sourceMessage?.sources && section.sourceMessage.sources.length > 0 && `\n\nCitations:\n${section.sourceMessage.sources?.map((source: any, i: any) => `[${i + 1}] ${source.metadata.url}`).join(`\n`)}`}`;
 | 
					        const contentToCopy = `${initialMessage}${message.sources && message.sources.length > 0 && `\n\nCitations:\n${message.sources?.map((source: any, i: any) => `[${i + 1}] ${source.metadata.url}`).join(`\n`)}`}`;
 | 
				
			||||||
        navigator.clipboard.writeText(contentToCopy);
 | 
					        navigator.clipboard.writeText(contentToCopy);
 | 
				
			||||||
        setCopied(true);
 | 
					        setCopied(true);
 | 
				
			||||||
        setTimeout(() => setCopied(false), 1000);
 | 
					        setTimeout(() => setCopied(false), 1000);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,8 @@
 | 
				
			|||||||
'use client';
 | 
					'use client';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* eslint-disable @next/next/no-img-element */
 | 
					/* eslint-disable @next/next/no-img-element */
 | 
				
			||||||
import React, { MutableRefObject } from 'react';
 | 
					import React, { MutableRefObject, useEffect, useState } from 'react';
 | 
				
			||||||
 | 
					import { Message } from './ChatWindow';
 | 
				
			||||||
import { cn } from '@/lib/utils';
 | 
					import { cn } from '@/lib/utils';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  BookCopy,
 | 
					  BookCopy,
 | 
				
			||||||
@@ -19,37 +20,90 @@ import SearchImages from './SearchImages';
 | 
				
			|||||||
import SearchVideos from './SearchVideos';
 | 
					import SearchVideos from './SearchVideos';
 | 
				
			||||||
import { useSpeech } from 'react-text-to-speech';
 | 
					import { useSpeech } from 'react-text-to-speech';
 | 
				
			||||||
import ThinkBox from './ThinkBox';
 | 
					import ThinkBox from './ThinkBox';
 | 
				
			||||||
import { useChat, Section } from '@/lib/hooks/useChat';
 | 
					 | 
				
			||||||
import Citation from './Citation';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const ThinkTagProcessor = ({
 | 
					const ThinkTagProcessor = ({ children }: { children: React.ReactNode }) => {
 | 
				
			||||||
  children,
 | 
					  return <ThinkBox content={children as string} />;
 | 
				
			||||||
  thinkingEnded,
 | 
					 | 
				
			||||||
}: {
 | 
					 | 
				
			||||||
  children: React.ReactNode;
 | 
					 | 
				
			||||||
  thinkingEnded: boolean;
 | 
					 | 
				
			||||||
}) => {
 | 
					 | 
				
			||||||
  return (
 | 
					 | 
				
			||||||
    <ThinkBox content={children as string} thinkingEnded={thinkingEnded} />
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const MessageBox = ({
 | 
					const MessageBox = ({
 | 
				
			||||||
  section,
 | 
					  message,
 | 
				
			||||||
  sectionIndex,
 | 
					  messageIndex,
 | 
				
			||||||
 | 
					  history,
 | 
				
			||||||
 | 
					  loading,
 | 
				
			||||||
  dividerRef,
 | 
					  dividerRef,
 | 
				
			||||||
  isLast,
 | 
					  isLast,
 | 
				
			||||||
 | 
					  rewrite,
 | 
				
			||||||
 | 
					  sendMessage,
 | 
				
			||||||
}: {
 | 
					}: {
 | 
				
			||||||
  section: Section;
 | 
					  message: Message;
 | 
				
			||||||
  sectionIndex: number;
 | 
					  messageIndex: number;
 | 
				
			||||||
 | 
					  history: Message[];
 | 
				
			||||||
 | 
					  loading: boolean;
 | 
				
			||||||
  dividerRef?: MutableRefObject<HTMLDivElement | null>;
 | 
					  dividerRef?: MutableRefObject<HTMLDivElement | null>;
 | 
				
			||||||
  isLast: boolean;
 | 
					  isLast: boolean;
 | 
				
			||||||
 | 
					  rewrite: (messageId: string) => void;
 | 
				
			||||||
 | 
					  sendMessage: (message: string) => void;
 | 
				
			||||||
}) => {
 | 
					}) => {
 | 
				
			||||||
  const { loading, chatTurns, sendMessage, rewrite } = useChat();
 | 
					  const [parsedMessage, setParsedMessage] = useState(message.content);
 | 
				
			||||||
 | 
					  const [speechMessage, setSpeechMessage] = useState(message.content);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const parsedMessage = section.parsedAssistantMessage || '';
 | 
					  useEffect(() => {
 | 
				
			||||||
  const speechMessage = section.speechMessage || '';
 | 
					    const citationRegex = /\[([^\]]+)\]/g;
 | 
				
			||||||
  const thinkingEnded = section.thinkingEnded;
 | 
					    const regex = /\[(\d+)\]/g;
 | 
				
			||||||
 | 
					    let processedMessage = message.content;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (message.role === 'assistant' && message.content.includes('<think>')) {
 | 
				
			||||||
 | 
					      const openThinkTag = processedMessage.match(/<think>/g)?.length || 0;
 | 
				
			||||||
 | 
					      const closeThinkTag = processedMessage.match(/<\/think>/g)?.length || 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (openThinkTag > closeThinkTag) {
 | 
				
			||||||
 | 
					        processedMessage += '</think> <a> </a>'; // The extra <a> </a> is to prevent the the think component from looking bad
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (
 | 
				
			||||||
 | 
					      message.role === 'assistant' &&
 | 
				
			||||||
 | 
					      message?.sources &&
 | 
				
			||||||
 | 
					      message.sources.length > 0
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					      setParsedMessage(
 | 
				
			||||||
 | 
					        processedMessage.replace(
 | 
				
			||||||
 | 
					          citationRegex,
 | 
				
			||||||
 | 
					          (_, capturedContent: string) => {
 | 
				
			||||||
 | 
					            const numbers = capturedContent
 | 
				
			||||||
 | 
					              .split(',')
 | 
				
			||||||
 | 
					              .map((numStr) => numStr.trim());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const linksHtml = numbers
 | 
				
			||||||
 | 
					              .map((numStr) => {
 | 
				
			||||||
 | 
					                const number = parseInt(numStr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (isNaN(number) || number <= 0) {
 | 
				
			||||||
 | 
					                  return `[${numStr}]`;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const source = message.sources?.[number - 1];
 | 
				
			||||||
 | 
					                const url = source?.metadata?.url;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (url) {
 | 
				
			||||||
 | 
					                  return `<a href="${url}" target="_blank" className="bg-light-secondary dark:bg-dark-secondary px-1 rounded ml-1 no-underline text-xs text-black/70 dark:text-white/70 relative">${numStr}</a>`;
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                  return `[${numStr}]`;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					              })
 | 
				
			||||||
 | 
					              .join('');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return linksHtml;
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      setSpeechMessage(message.content.replace(regex, ''));
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setSpeechMessage(message.content.replace(regex, ''));
 | 
				
			||||||
 | 
					    setParsedMessage(processedMessage);
 | 
				
			||||||
 | 
					  }, [message.content, message.sources, message.role]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const { speechStatus, start, stop } = useSpeech({ text: speechMessage });
 | 
					  const { speechStatus, start, stop } = useSpeech({ text: speechMessage });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -57,31 +111,33 @@ const MessageBox = ({
 | 
				
			|||||||
    overrides: {
 | 
					    overrides: {
 | 
				
			||||||
      think: {
 | 
					      think: {
 | 
				
			||||||
        component: ThinkTagProcessor,
 | 
					        component: ThinkTagProcessor,
 | 
				
			||||||
        props: {
 | 
					 | 
				
			||||||
          thinkingEnded: thinkingEnded,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      citation: {
 | 
					 | 
				
			||||||
        component: Citation,
 | 
					 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <div className="space-y-6">
 | 
					    <div>
 | 
				
			||||||
      <div className={'w-full pt-8 break-words'}>
 | 
					      {message.role === 'user' && (
 | 
				
			||||||
        <h2 className="text-black dark:text-white font-medium text-3xl lg:w-9/12">
 | 
					 | 
				
			||||||
          {section.userMessage.content}
 | 
					 | 
				
			||||||
        </h2>
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      <div className="flex flex-col space-y-9 lg:space-y-0 lg:flex-row lg:justify-between lg:space-x-9">
 | 
					 | 
				
			||||||
        <div
 | 
					        <div
 | 
				
			||||||
          ref={dividerRef}
 | 
					          className={cn(
 | 
				
			||||||
          className="flex flex-col space-y-6 w-full lg:w-9/12"
 | 
					            'w-full',
 | 
				
			||||||
 | 
					            messageIndex === 0 ? 'pt-16' : 'pt-8',
 | 
				
			||||||
 | 
					            'break-words',
 | 
				
			||||||
 | 
					          )}
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
          {section.sourceMessage &&
 | 
					          <h2 className="text-black dark:text-white font-medium text-3xl lg:w-9/12">
 | 
				
			||||||
            section.sourceMessage.sources.length > 0 && (
 | 
					            {message.content}
 | 
				
			||||||
 | 
					          </h2>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      {message.role === 'assistant' && (
 | 
				
			||||||
 | 
					        <div className="flex flex-col space-y-9 lg:space-y-0 lg:flex-row lg:justify-between lg:space-x-9">
 | 
				
			||||||
 | 
					          <div
 | 
				
			||||||
 | 
					            ref={dividerRef}
 | 
				
			||||||
 | 
					            className="flex flex-col space-y-6 w-full lg:w-9/12"
 | 
				
			||||||
 | 
					          >
 | 
				
			||||||
 | 
					            {message.sources && message.sources.length > 0 && (
 | 
				
			||||||
              <div className="flex flex-col space-y-2">
 | 
					              <div className="flex flex-col space-y-2">
 | 
				
			||||||
                <div className="flex flex-row items-center space-x-2">
 | 
					                <div className="flex flex-row items-center space-x-2">
 | 
				
			||||||
                  <BookCopy className="text-black dark:text-white" size={20} />
 | 
					                  <BookCopy className="text-black dark:text-white" size={20} />
 | 
				
			||||||
@@ -89,12 +145,10 @@ const MessageBox = ({
 | 
				
			|||||||
                    Sources
 | 
					                    Sources
 | 
				
			||||||
                  </h3>
 | 
					                  </h3>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
                <MessageSources sources={section.sourceMessage.sources} />
 | 
					                <MessageSources sources={message.sources} />
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
            )}
 | 
					            )}
 | 
				
			||||||
 | 
					            <div className="flex flex-col space-y-2">
 | 
				
			||||||
          <div className="flex flex-col space-y-2">
 | 
					 | 
				
			||||||
            {section.sourceMessage && (
 | 
					 | 
				
			||||||
              <div className="flex flex-row items-center space-x-2">
 | 
					              <div className="flex flex-row items-center space-x-2">
 | 
				
			||||||
                <Disc3
 | 
					                <Disc3
 | 
				
			||||||
                  className={cn(
 | 
					                  className={cn(
 | 
				
			||||||
@@ -107,115 +161,100 @@ const MessageBox = ({
 | 
				
			|||||||
                  Answer
 | 
					                  Answer
 | 
				
			||||||
                </h3>
 | 
					                </h3>
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
            )}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            {section.assistantMessage && (
 | 
					              <Markdown
 | 
				
			||||||
              <>
 | 
					                className={cn(
 | 
				
			||||||
                <Markdown
 | 
					                  'prose prose-h1:mb-3 prose-h2:mb-2 prose-h2:mt-6 prose-h2:font-[800] prose-h3:mt-4 prose-h3:mb-1.5 prose-h3:font-[600] dark:prose-invert prose-p:leading-relaxed prose-pre:p-0 font-[400]',
 | 
				
			||||||
                  className={cn(
 | 
					                  'max-w-none break-words text-black dark:text-white',
 | 
				
			||||||
                    'prose prose-h1:mb-3 prose-h2:mb-2 prose-h2:mt-6 prose-h2:font-[800] prose-h3:mt-4 prose-h3:mb-1.5 prose-h3:font-[600] dark:prose-invert prose-p:leading-relaxed prose-pre:p-0 font-[400]',
 | 
					 | 
				
			||||||
                    'max-w-none break-words text-black dark:text-white',
 | 
					 | 
				
			||||||
                  )}
 | 
					 | 
				
			||||||
                  options={markdownOverrides}
 | 
					 | 
				
			||||||
                >
 | 
					 | 
				
			||||||
                  {parsedMessage}
 | 
					 | 
				
			||||||
                </Markdown>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                {loading && isLast ? null : (
 | 
					 | 
				
			||||||
                  <div className="flex flex-row items-center justify-between w-full text-black dark:text-white py-4 -mx-2">
 | 
					 | 
				
			||||||
                    <div className="flex flex-row items-center space-x-1">
 | 
					 | 
				
			||||||
                      <Rewrite
 | 
					 | 
				
			||||||
                        rewrite={rewrite}
 | 
					 | 
				
			||||||
                        messageId={section.assistantMessage.messageId}
 | 
					 | 
				
			||||||
                      />
 | 
					 | 
				
			||||||
                    </div>
 | 
					 | 
				
			||||||
                    <div className="flex flex-row items-center space-x-1">
 | 
					 | 
				
			||||||
                      <Copy
 | 
					 | 
				
			||||||
                        initialMessage={section.assistantMessage.content}
 | 
					 | 
				
			||||||
                        section={section}
 | 
					 | 
				
			||||||
                      />
 | 
					 | 
				
			||||||
                      <button
 | 
					 | 
				
			||||||
                        onClick={() => {
 | 
					 | 
				
			||||||
                          if (speechStatus === 'started') {
 | 
					 | 
				
			||||||
                            stop();
 | 
					 | 
				
			||||||
                          } else {
 | 
					 | 
				
			||||||
                            start();
 | 
					 | 
				
			||||||
                          }
 | 
					 | 
				
			||||||
                        }}
 | 
					 | 
				
			||||||
                        className="p-2 text-black/70 dark:text-white/70 rounded-xl hover:bg-light-secondary dark:hover:bg-dark-secondary transition duration-200 hover:text-black dark:hover:text-white"
 | 
					 | 
				
			||||||
                      >
 | 
					 | 
				
			||||||
                        {speechStatus === 'started' ? (
 | 
					 | 
				
			||||||
                          <StopCircle size={18} />
 | 
					 | 
				
			||||||
                        ) : (
 | 
					 | 
				
			||||||
                          <Volume2 size={18} />
 | 
					 | 
				
			||||||
                        )}
 | 
					 | 
				
			||||||
                      </button>
 | 
					 | 
				
			||||||
                    </div>
 | 
					 | 
				
			||||||
                  </div>
 | 
					 | 
				
			||||||
                )}
 | 
					                )}
 | 
				
			||||||
 | 
					                options={markdownOverrides}
 | 
				
			||||||
                {isLast &&
 | 
					              >
 | 
				
			||||||
                  section.suggestions &&
 | 
					                {parsedMessage}
 | 
				
			||||||
                  section.suggestions.length > 0 &&
 | 
					              </Markdown>
 | 
				
			||||||
                  section.assistantMessage &&
 | 
					              {loading && isLast ? null : (
 | 
				
			||||||
                  !loading && (
 | 
					                <div className="flex flex-row items-center justify-between w-full text-black dark:text-white py-4 -mx-2">
 | 
				
			||||||
                    <div className="mt-8 pt-6 border-t border-light-200/50 dark:border-dark-200/50">
 | 
					                  <div className="flex flex-row items-center space-x-1">
 | 
				
			||||||
                      <div className="flex flex-row items-center space-x-2 mb-4">
 | 
					                    {/*  <button className="p-2 text-black/70 dark:text-white/70 rounded-xl hover:bg-light-secondary dark:hover:bg-dark-secondary transition duration-200 hover:text-black text-black dark:hover:text-white">
 | 
				
			||||||
                        <Layers3
 | 
					                      <Share size={18} />
 | 
				
			||||||
                          className="text-black dark:text-white"
 | 
					                    </button> */}
 | 
				
			||||||
                          size={20}
 | 
					                    <Rewrite rewrite={rewrite} messageId={message.messageId} />
 | 
				
			||||||
                        />
 | 
					                  </div>
 | 
				
			||||||
                        <h3 className="text-black dark:text-white font-medium text-xl">
 | 
					                  <div className="flex flex-row items-center space-x-1">
 | 
				
			||||||
                          Related
 | 
					                    <Copy initialMessage={message.content} message={message} />
 | 
				
			||||||
                        </h3>
 | 
					                    <button
 | 
				
			||||||
 | 
					                      onClick={() => {
 | 
				
			||||||
 | 
					                        if (speechStatus === 'started') {
 | 
				
			||||||
 | 
					                          stop();
 | 
				
			||||||
 | 
					                        } else {
 | 
				
			||||||
 | 
					                          start();
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                      }}
 | 
				
			||||||
 | 
					                      className="p-2 text-black/70 dark:text-white/70 rounded-xl hover:bg-light-secondary dark:hover:bg-dark-secondary transition duration-200 hover:text-black dark:hover:text-white"
 | 
				
			||||||
 | 
					                    >
 | 
				
			||||||
 | 
					                      {speechStatus === 'started' ? (
 | 
				
			||||||
 | 
					                        <StopCircle size={18} />
 | 
				
			||||||
 | 
					                      ) : (
 | 
				
			||||||
 | 
					                        <Volume2 size={18} />
 | 
				
			||||||
 | 
					                      )}
 | 
				
			||||||
 | 
					                    </button>
 | 
				
			||||||
 | 
					                  </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					              )}
 | 
				
			||||||
 | 
					              {isLast &&
 | 
				
			||||||
 | 
					                message.suggestions &&
 | 
				
			||||||
 | 
					                message.suggestions.length > 0 &&
 | 
				
			||||||
 | 
					                message.role === 'assistant' &&
 | 
				
			||||||
 | 
					                !loading && (
 | 
				
			||||||
 | 
					                  <>
 | 
				
			||||||
 | 
					                    <div className="h-px w-full bg-light-secondary dark:bg-dark-secondary" />
 | 
				
			||||||
 | 
					                    <div className="flex flex-col space-y-3 text-black dark:text-white">
 | 
				
			||||||
 | 
					                      <div className="flex flex-row items-center space-x-2 mt-4">
 | 
				
			||||||
 | 
					                        <Layers3 />
 | 
				
			||||||
 | 
					                        <h3 className="text-xl font-medium">Related</h3>
 | 
				
			||||||
                      </div>
 | 
					                      </div>
 | 
				
			||||||
                      <div className="space-y-0">
 | 
					                      <div className="flex flex-col space-y-3">
 | 
				
			||||||
                        {section.suggestions.map(
 | 
					                        {message.suggestions.map((suggestion, i) => (
 | 
				
			||||||
                          (suggestion: string, i: number) => (
 | 
					                          <div
 | 
				
			||||||
                            <div key={i}>
 | 
					                            className="flex flex-col space-y-3 text-sm"
 | 
				
			||||||
                              {i > 0 && (
 | 
					                            key={i}
 | 
				
			||||||
                                <div className="h-px bg-light-200/40 dark:bg-dark-200/40 mx-3" />
 | 
					                          >
 | 
				
			||||||
                              )}
 | 
					                            <div className="h-px w-full bg-light-secondary dark:bg-dark-secondary" />
 | 
				
			||||||
                              <button
 | 
					                            <div
 | 
				
			||||||
                                onClick={() => sendMessage(suggestion)}
 | 
					                              onClick={() => {
 | 
				
			||||||
                                className="group w-full px-3 py-4 text-left transition-colors duration-200"
 | 
					                                sendMessage(suggestion);
 | 
				
			||||||
                              >
 | 
					                              }}
 | 
				
			||||||
                                <div className="flex items-center justify-between gap-3">
 | 
					                              className="cursor-pointer flex flex-row justify-between font-medium space-x-2 items-center"
 | 
				
			||||||
                                  <p className="text-sm text-black/70 dark:text-white/70 group-hover:text-[#24A0ED] transition-colors duration-200 leading-relaxed">
 | 
					                            >
 | 
				
			||||||
                                    {suggestion}
 | 
					                              <p className="transition duration-200 hover:text-[#24A0ED]">
 | 
				
			||||||
                                  </p>
 | 
					                                {suggestion}
 | 
				
			||||||
                                  <Plus
 | 
					                              </p>
 | 
				
			||||||
                                    size={16}
 | 
					                              <Plus
 | 
				
			||||||
                                    className="text-black/40 dark:text-white/40 group-hover:text-[#24A0ED] transition-colors duration-200 flex-shrink-0"
 | 
					                                size={20}
 | 
				
			||||||
                                  />
 | 
					                                className="text-[#24A0ED] flex-shrink-0"
 | 
				
			||||||
                                </div>
 | 
					                              />
 | 
				
			||||||
                              </button>
 | 
					 | 
				
			||||||
                            </div>
 | 
					                            </div>
 | 
				
			||||||
                          ),
 | 
					                          </div>
 | 
				
			||||||
                        )}
 | 
					                        ))}
 | 
				
			||||||
                      </div>
 | 
					                      </div>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                  )}
 | 
					                  </>
 | 
				
			||||||
              </>
 | 
					                )}
 | 
				
			||||||
            )}
 | 
					            </div>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        {section.assistantMessage && (
 | 
					 | 
				
			||||||
          <div className="lg:sticky lg:top-20 flex flex-col items-center space-y-3 w-full lg:w-3/12 z-30 h-full pb-4">
 | 
					          <div className="lg:sticky lg:top-20 flex flex-col items-center space-y-3 w-full lg:w-3/12 z-30 h-full pb-4">
 | 
				
			||||||
            <SearchImages
 | 
					            <SearchImages
 | 
				
			||||||
              query={section.userMessage.content}
 | 
					              query={history[messageIndex - 1].content}
 | 
				
			||||||
              chatHistory={chatTurns.slice(0, sectionIndex * 2)}
 | 
					              chatHistory={history.slice(0, messageIndex - 1)}
 | 
				
			||||||
              messageId={section.assistantMessage.messageId}
 | 
					              messageId={message.messageId}
 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
            <SearchVideos
 | 
					            <SearchVideos
 | 
				
			||||||
              chatHistory={chatTurns.slice(0, sectionIndex * 2)}
 | 
					              chatHistory={history.slice(0, messageIndex - 1)}
 | 
				
			||||||
              query={section.userMessage.content}
 | 
					              query={history[messageIndex - 1].content}
 | 
				
			||||||
              messageId={section.assistantMessage.messageId}
 | 
					              messageId={message.messageId}
 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        )}
 | 
					        </div>
 | 
				
			||||||
      </div>
 | 
					      )}
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,11 +6,22 @@ import Attach from './MessageInputActions/Attach';
 | 
				
			|||||||
import CopilotToggle from './MessageInputActions/Copilot';
 | 
					import CopilotToggle from './MessageInputActions/Copilot';
 | 
				
			||||||
import { File } from './ChatWindow';
 | 
					import { File } from './ChatWindow';
 | 
				
			||||||
import AttachSmall from './MessageInputActions/AttachSmall';
 | 
					import AttachSmall from './MessageInputActions/AttachSmall';
 | 
				
			||||||
import { useChat } from '@/lib/hooks/useChat';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const MessageInput = () => {
 | 
					 | 
				
			||||||
  const { loading, sendMessage } = useChat();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const MessageInput = ({
 | 
				
			||||||
 | 
					  sendMessage,
 | 
				
			||||||
 | 
					  loading,
 | 
				
			||||||
 | 
					  fileIds,
 | 
				
			||||||
 | 
					  setFileIds,
 | 
				
			||||||
 | 
					  files,
 | 
				
			||||||
 | 
					  setFiles,
 | 
				
			||||||
 | 
					}: {
 | 
				
			||||||
 | 
					  sendMessage: (message: string) => void;
 | 
				
			||||||
 | 
					  loading: boolean;
 | 
				
			||||||
 | 
					  fileIds: string[];
 | 
				
			||||||
 | 
					  setFileIds: (fileIds: string[]) => void;
 | 
				
			||||||
 | 
					  files: File[];
 | 
				
			||||||
 | 
					  setFiles: (files: File[]) => void;
 | 
				
			||||||
 | 
					}) => {
 | 
				
			||||||
  const [copilotEnabled, setCopilotEnabled] = useState(false);
 | 
					  const [copilotEnabled, setCopilotEnabled] = useState(false);
 | 
				
			||||||
  const [message, setMessage] = useState('');
 | 
					  const [message, setMessage] = useState('');
 | 
				
			||||||
  const [textareaRows, setTextareaRows] = useState(1);
 | 
					  const [textareaRows, setTextareaRows] = useState(1);
 | 
				
			||||||
@@ -64,11 +75,18 @@ const MessageInput = () => {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }}
 | 
					      }}
 | 
				
			||||||
      className={cn(
 | 
					      className={cn(
 | 
				
			||||||
        'bg-light-secondary dark:bg-dark-secondary p-4 flex items-center overflow-hidden border border-light-200 dark:border-dark-200 shadow-sm shadow-light-200/10 dark:shadow-black/20 transition-all duration-200 focus-within:border-light-300 dark:focus-within:border-dark-300',
 | 
					        'bg-light-secondary dark:bg-dark-secondary p-4 flex items-center overflow-hidden border border-light-200 dark:border-dark-200',
 | 
				
			||||||
        mode === 'multi' ? 'flex-col rounded-2xl' : 'flex-row rounded-full',
 | 
					        mode === 'multi' ? 'flex-col rounded-lg' : 'flex-row rounded-full',
 | 
				
			||||||
      )}
 | 
					      )}
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
      {mode === 'single' && <AttachSmall />}
 | 
					      {mode === 'single' && (
 | 
				
			||||||
 | 
					        <AttachSmall
 | 
				
			||||||
 | 
					          fileIds={fileIds}
 | 
				
			||||||
 | 
					          setFileIds={setFileIds}
 | 
				
			||||||
 | 
					          files={files}
 | 
				
			||||||
 | 
					          setFiles={setFiles}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
      <TextareaAutosize
 | 
					      <TextareaAutosize
 | 
				
			||||||
        ref={inputRef}
 | 
					        ref={inputRef}
 | 
				
			||||||
        value={message}
 | 
					        value={message}
 | 
				
			||||||
@@ -95,7 +113,12 @@ const MessageInput = () => {
 | 
				
			|||||||
      )}
 | 
					      )}
 | 
				
			||||||
      {mode === 'multi' && (
 | 
					      {mode === 'multi' && (
 | 
				
			||||||
        <div className="flex flex-row items-center justify-between w-full pt-2">
 | 
					        <div className="flex flex-row items-center justify-between w-full pt-2">
 | 
				
			||||||
          <AttachSmall />
 | 
					          <AttachSmall
 | 
				
			||||||
 | 
					            fileIds={fileIds}
 | 
				
			||||||
 | 
					            setFileIds={setFileIds}
 | 
				
			||||||
 | 
					            files={files}
 | 
				
			||||||
 | 
					            setFiles={setFiles}
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
          <div className="flex flex-row items-center space-x-4">
 | 
					          <div className="flex flex-row items-center space-x-4">
 | 
				
			||||||
            <CopilotToggle
 | 
					            <CopilotToggle
 | 
				
			||||||
              copilotEnabled={copilotEnabled}
 | 
					              copilotEnabled={copilotEnabled}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,21 +5,23 @@ import {
 | 
				
			|||||||
  PopoverPanel,
 | 
					  PopoverPanel,
 | 
				
			||||||
  Transition,
 | 
					  Transition,
 | 
				
			||||||
} from '@headlessui/react';
 | 
					} from '@headlessui/react';
 | 
				
			||||||
import {
 | 
					import { CopyPlus, File, LoaderCircle, Plus, Trash } from 'lucide-react';
 | 
				
			||||||
  CopyPlus,
 | 
					 | 
				
			||||||
  File,
 | 
					 | 
				
			||||||
  Link,
 | 
					 | 
				
			||||||
  LoaderCircle,
 | 
					 | 
				
			||||||
  Paperclip,
 | 
					 | 
				
			||||||
  Plus,
 | 
					 | 
				
			||||||
  Trash,
 | 
					 | 
				
			||||||
} from 'lucide-react';
 | 
					 | 
				
			||||||
import { Fragment, useRef, useState } from 'react';
 | 
					import { Fragment, useRef, useState } from 'react';
 | 
				
			||||||
import { useChat } from '@/lib/hooks/useChat';
 | 
					import { File as FileType } from '../ChatWindow';
 | 
				
			||||||
 | 
					 | 
				
			||||||
const Attach = () => {
 | 
					 | 
				
			||||||
  const { files, setFiles, setFileIds, fileIds } = useChat();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Attach = ({
 | 
				
			||||||
 | 
					  fileIds,
 | 
				
			||||||
 | 
					  setFileIds,
 | 
				
			||||||
 | 
					  showText,
 | 
				
			||||||
 | 
					  files,
 | 
				
			||||||
 | 
					  setFiles,
 | 
				
			||||||
 | 
					}: {
 | 
				
			||||||
 | 
					  fileIds: string[];
 | 
				
			||||||
 | 
					  setFileIds: (fileIds: string[]) => void;
 | 
				
			||||||
 | 
					  showText?: boolean;
 | 
				
			||||||
 | 
					  files: FileType[];
 | 
				
			||||||
 | 
					  setFiles: (files: FileType[]) => void;
 | 
				
			||||||
 | 
					}) => {
 | 
				
			||||||
  const [loading, setLoading] = useState(false);
 | 
					  const [loading, setLoading] = useState(false);
 | 
				
			||||||
  const fileInputRef = useRef<any>();
 | 
					  const fileInputRef = useRef<any>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -32,12 +34,12 @@ const Attach = () => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const embeddingModelProvider = localStorage.getItem(
 | 
					    const embeddingModelProvider = localStorage.getItem(
 | 
				
			||||||
      'embeddingModelProviderId',
 | 
					      'embeddingModelProvider',
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    const embeddingModel = localStorage.getItem('embeddingModelKey');
 | 
					    const embeddingModel = localStorage.getItem('embeddingModel');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    data.append('embedding_model_provider_id', embeddingModelProvider!);
 | 
					    data.append('embedding_model_provider', embeddingModelProvider!);
 | 
				
			||||||
    data.append('embedding_model_key', embeddingModel!);
 | 
					    data.append('embedding_model', embeddingModel!);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const res = await fetch(`/api/uploads`, {
 | 
					    const res = await fetch(`/api/uploads`, {
 | 
				
			||||||
      method: 'POST',
 | 
					      method: 'POST',
 | 
				
			||||||
@@ -52,16 +54,42 @@ const Attach = () => {
 | 
				
			|||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return loading ? (
 | 
					  return loading ? (
 | 
				
			||||||
    <div className="active:border-none hover:bg-light-200 hover:dark:bg-dark-200 p-2 rounded-lg focus:outline-none text-black/50 dark:text-white/50 transition duration-200">
 | 
					    <div className="flex flex-row items-center justify-between space-x-1">
 | 
				
			||||||
      <LoaderCircle size={16} className="text-sky-400 animate-spin" />
 | 
					      <LoaderCircle size={18} className="text-sky-400 animate-spin" />
 | 
				
			||||||
 | 
					      <p className="text-sky-400 inline whitespace-nowrap text-xs font-medium">
 | 
				
			||||||
 | 
					        Uploading..
 | 
				
			||||||
 | 
					      </p>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  ) : files.length > 0 ? (
 | 
					  ) : files.length > 0 ? (
 | 
				
			||||||
    <Popover className="relative w-full max-w-[15rem] md:max-w-md lg:max-w-lg">
 | 
					    <Popover className="relative w-full max-w-[15rem] md:max-w-md lg:max-w-lg">
 | 
				
			||||||
      <PopoverButton
 | 
					      <PopoverButton
 | 
				
			||||||
        type="button"
 | 
					        type="button"
 | 
				
			||||||
        className="active:border-none hover:bg-light-200 hover:dark:bg-dark-200 p-2 rounded-lg focus:outline-none headless-open:text-black dark:headless-open:text-white text-black/50 dark:text-white/50 active:scale-95 transition duration-200 hover:text-black dark:hover:text-white"
 | 
					        className={cn(
 | 
				
			||||||
 | 
					          'flex flex-row items-center justify-between space-x-1 p-2 text-black/50 dark:text-white/50 rounded-xl hover:bg-light-secondary dark:hover:bg-dark-secondary active:scale-95 transition duration-200 hover:text-black dark:hover:text-white',
 | 
				
			||||||
 | 
					          files.length > 0 ? '-ml-2 lg:-ml-3' : '',
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        <File size={16} className="text-sky-400" />
 | 
					        {files.length > 1 && (
 | 
				
			||||||
 | 
					          <>
 | 
				
			||||||
 | 
					            <File size={19} className="text-sky-400" />
 | 
				
			||||||
 | 
					            <p className="text-sky-400 inline whitespace-nowrap text-xs font-medium">
 | 
				
			||||||
 | 
					              {files.length} files
 | 
				
			||||||
 | 
					            </p>
 | 
				
			||||||
 | 
					          </>
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        {files.length === 1 && (
 | 
				
			||||||
 | 
					          <>
 | 
				
			||||||
 | 
					            <File size={18} className="text-sky-400" />
 | 
				
			||||||
 | 
					            <p className="text-sky-400 text-xs font-medium">
 | 
				
			||||||
 | 
					              {files[0].fileName.length > 10
 | 
				
			||||||
 | 
					                ? files[0].fileName.replace(/\.\w+$/, '').substring(0, 3) +
 | 
				
			||||||
 | 
					                  '...' +
 | 
				
			||||||
 | 
					                  files[0].fileExtension
 | 
				
			||||||
 | 
					                : files[0].fileName}
 | 
				
			||||||
 | 
					            </p>
 | 
				
			||||||
 | 
					          </>
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
      </PopoverButton>
 | 
					      </PopoverButton>
 | 
				
			||||||
      <Transition
 | 
					      <Transition
 | 
				
			||||||
        as={Fragment}
 | 
					        as={Fragment}
 | 
				
			||||||
@@ -82,7 +110,7 @@ const Attach = () => {
 | 
				
			|||||||
                <button
 | 
					                <button
 | 
				
			||||||
                  type="button"
 | 
					                  type="button"
 | 
				
			||||||
                  onClick={() => fileInputRef.current.click()}
 | 
					                  onClick={() => fileInputRef.current.click()}
 | 
				
			||||||
                  className="flex flex-row items-center space-x-1 text-black/70 dark:text-white/70 hover:text-black hover:dark:text-white transition duration-200 focus:outline-none"
 | 
					                  className="flex flex-row items-center space-x-1 text-black/70 dark:text-white/70 hover:text-black hover:dark:text-white transition duration-200"
 | 
				
			||||||
                >
 | 
					                >
 | 
				
			||||||
                  <input
 | 
					                  <input
 | 
				
			||||||
                    type="file"
 | 
					                    type="file"
 | 
				
			||||||
@@ -92,7 +120,7 @@ const Attach = () => {
 | 
				
			|||||||
                    multiple
 | 
					                    multiple
 | 
				
			||||||
                    hidden
 | 
					                    hidden
 | 
				
			||||||
                  />
 | 
					                  />
 | 
				
			||||||
                  <Plus size={16} />
 | 
					                  <Plus size={18} />
 | 
				
			||||||
                  <p className="text-xs">Add</p>
 | 
					                  <p className="text-xs">Add</p>
 | 
				
			||||||
                </button>
 | 
					                </button>
 | 
				
			||||||
                <button
 | 
					                <button
 | 
				
			||||||
@@ -100,7 +128,7 @@ const Attach = () => {
 | 
				
			|||||||
                    setFiles([]);
 | 
					                    setFiles([]);
 | 
				
			||||||
                    setFileIds([]);
 | 
					                    setFileIds([]);
 | 
				
			||||||
                  }}
 | 
					                  }}
 | 
				
			||||||
                  className="flex flex-row items-center space-x-1 text-black/70 dark:text-white/70 hover:text-black hover:dark:text-white transition duration-200 focus:outline-none"
 | 
					                  className="flex flex-row items-center space-x-1 text-black/70 dark:text-white/70 hover:text-black hover:dark:text-white transition duration-200"
 | 
				
			||||||
                >
 | 
					                >
 | 
				
			||||||
                  <Trash size={14} />
 | 
					                  <Trash size={14} />
 | 
				
			||||||
                  <p className="text-xs">Clear</p>
 | 
					                  <p className="text-xs">Clear</p>
 | 
				
			||||||
@@ -114,11 +142,8 @@ const Attach = () => {
 | 
				
			|||||||
                  key={i}
 | 
					                  key={i}
 | 
				
			||||||
                  className="flex flex-row items-center justify-start w-full space-x-3 p-3"
 | 
					                  className="flex flex-row items-center justify-start w-full space-x-3 p-3"
 | 
				
			||||||
                >
 | 
					                >
 | 
				
			||||||
                  <div className="bg-light-100 dark:bg-dark-100 flex items-center justify-center w-10 h-10 rounded-md">
 | 
					                  <div className="bg-dark-100 flex items-center justify-center w-10 h-10 rounded-md">
 | 
				
			||||||
                    <File
 | 
					                    <File size={16} className="text-white/70" />
 | 
				
			||||||
                      size={16}
 | 
					 | 
				
			||||||
                      className="text-black/70 dark:text-white/70"
 | 
					 | 
				
			||||||
                    />
 | 
					 | 
				
			||||||
                  </div>
 | 
					                  </div>
 | 
				
			||||||
                  <p className="text-black/70 dark:text-white/70 text-sm">
 | 
					                  <p className="text-black/70 dark:text-white/70 text-sm">
 | 
				
			||||||
                    {file.fileName.length > 25
 | 
					                    {file.fileName.length > 25
 | 
				
			||||||
@@ -139,7 +164,8 @@ const Attach = () => {
 | 
				
			|||||||
      type="button"
 | 
					      type="button"
 | 
				
			||||||
      onClick={() => fileInputRef.current.click()}
 | 
					      onClick={() => fileInputRef.current.click()}
 | 
				
			||||||
      className={cn(
 | 
					      className={cn(
 | 
				
			||||||
        'flex items-center justify-center active:border-none hover:bg-light-200 hover:dark:bg-dark-200 p-2 rounded-lg focus:outline-none headless-open:text-black dark:headless-open:text-white text-black/50 dark:text-white/50 active:scale-95 transition duration-200 hover:text-black dark:hover:text-white',
 | 
					        'flex flex-row items-center space-x-1 text-black/50 dark:text-white/50 rounded-xl hover:bg-light-secondary dark:hover:bg-dark-secondary transition duration-200 hover:text-black dark:hover:text-white',
 | 
				
			||||||
 | 
					        showText ? '' : 'p-2',
 | 
				
			||||||
      )}
 | 
					      )}
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
      <input
 | 
					      <input
 | 
				
			||||||
@@ -150,7 +176,8 @@ const Attach = () => {
 | 
				
			|||||||
        multiple
 | 
					        multiple
 | 
				
			||||||
        hidden
 | 
					        hidden
 | 
				
			||||||
      />
 | 
					      />
 | 
				
			||||||
      <Paperclip size={16} />
 | 
					      <CopyPlus size={showText ? 18 : undefined} />
 | 
				
			||||||
 | 
					      {showText && <p className="text-xs font-medium pl-[1px]">Attach</p>}
 | 
				
			||||||
    </button>
 | 
					    </button>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,21 +5,21 @@ import {
 | 
				
			|||||||
  PopoverPanel,
 | 
					  PopoverPanel,
 | 
				
			||||||
  Transition,
 | 
					  Transition,
 | 
				
			||||||
} from '@headlessui/react';
 | 
					} from '@headlessui/react';
 | 
				
			||||||
import {
 | 
					import { CopyPlus, File, LoaderCircle, Plus, Trash } from 'lucide-react';
 | 
				
			||||||
  CopyPlus,
 | 
					 | 
				
			||||||
  File,
 | 
					 | 
				
			||||||
  LoaderCircle,
 | 
					 | 
				
			||||||
  Paperclip,
 | 
					 | 
				
			||||||
  Plus,
 | 
					 | 
				
			||||||
  Trash,
 | 
					 | 
				
			||||||
} from 'lucide-react';
 | 
					 | 
				
			||||||
import { Fragment, useRef, useState } from 'react';
 | 
					import { Fragment, useRef, useState } from 'react';
 | 
				
			||||||
import { File as FileType } from '../ChatWindow';
 | 
					import { File as FileType } from '../ChatWindow';
 | 
				
			||||||
import { useChat } from '@/lib/hooks/useChat';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const AttachSmall = () => {
 | 
					 | 
				
			||||||
  const { files, setFiles, setFileIds, fileIds } = useChat();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const AttachSmall = ({
 | 
				
			||||||
 | 
					  fileIds,
 | 
				
			||||||
 | 
					  setFileIds,
 | 
				
			||||||
 | 
					  files,
 | 
				
			||||||
 | 
					  setFiles,
 | 
				
			||||||
 | 
					}: {
 | 
				
			||||||
 | 
					  fileIds: string[];
 | 
				
			||||||
 | 
					  setFileIds: (fileIds: string[]) => void;
 | 
				
			||||||
 | 
					  files: FileType[];
 | 
				
			||||||
 | 
					  setFiles: (files: FileType[]) => void;
 | 
				
			||||||
 | 
					}) => {
 | 
				
			||||||
  const [loading, setLoading] = useState(false);
 | 
					  const [loading, setLoading] = useState(false);
 | 
				
			||||||
  const fileInputRef = useRef<any>();
 | 
					  const fileInputRef = useRef<any>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -32,12 +32,12 @@ const AttachSmall = () => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const embeddingModelProvider = localStorage.getItem(
 | 
					    const embeddingModelProvider = localStorage.getItem(
 | 
				
			||||||
      'embeddingModelProviderId',
 | 
					      'embeddingModelProvider',
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    const embeddingModel = localStorage.getItem('embeddingModelKey');
 | 
					    const embeddingModel = localStorage.getItem('embeddingModel');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    data.append('embedding_model_provider_id', embeddingModelProvider!);
 | 
					    data.append('embedding_model_provider', embeddingModelProvider!);
 | 
				
			||||||
    data.append('embedding_model_key', embeddingModel!);
 | 
					    data.append('embedding_model', embeddingModel!);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const res = await fetch(`/api/uploads`, {
 | 
					    const res = await fetch(`/api/uploads`, {
 | 
				
			||||||
      method: 'POST',
 | 
					      method: 'POST',
 | 
				
			||||||
@@ -52,7 +52,7 @@ const AttachSmall = () => {
 | 
				
			|||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return loading ? (
 | 
					  return loading ? (
 | 
				
			||||||
    <div className="flex flex-row items-center justify-between space-x-1 p-1 ">
 | 
					    <div className="flex flex-row items-center justify-between space-x-1 p-1">
 | 
				
			||||||
      <LoaderCircle size={20} className="text-sky-400 animate-spin" />
 | 
					      <LoaderCircle size={20} className="text-sky-400 animate-spin" />
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  ) : files.length > 0 ? (
 | 
					  ) : files.length > 0 ? (
 | 
				
			||||||
@@ -114,11 +114,8 @@ const AttachSmall = () => {
 | 
				
			|||||||
                  key={i}
 | 
					                  key={i}
 | 
				
			||||||
                  className="flex flex-row items-center justify-start w-full space-x-3 p-3"
 | 
					                  className="flex flex-row items-center justify-start w-full space-x-3 p-3"
 | 
				
			||||||
                >
 | 
					                >
 | 
				
			||||||
                  <div className="bg-light-100 dark:bg-dark-100 flex items-center justify-center w-10 h-10 rounded-md">
 | 
					                  <div className="bg-dark-100 flex items-center justify-center w-10 h-10 rounded-md">
 | 
				
			||||||
                    <File
 | 
					                    <File size={16} className="text-white/70" />
 | 
				
			||||||
                      size={16}
 | 
					 | 
				
			||||||
                      className="text-black/70 dark:text-white/70"
 | 
					 | 
				
			||||||
                    />
 | 
					 | 
				
			||||||
                  </div>
 | 
					                  </div>
 | 
				
			||||||
                  <p className="text-black/70 dark:text-white/70 text-sm">
 | 
					                  <p className="text-black/70 dark:text-white/70 text-sm">
 | 
				
			||||||
                    {file.fileName.length > 25
 | 
					                    {file.fileName.length > 25
 | 
				
			||||||
@@ -148,7 +145,7 @@ const AttachSmall = () => {
 | 
				
			|||||||
        multiple
 | 
					        multiple
 | 
				
			||||||
        hidden
 | 
					        hidden
 | 
				
			||||||
      />
 | 
					      />
 | 
				
			||||||
      <Paperclip size={16} />
 | 
					      <CopyPlus size={20} />
 | 
				
			||||||
    </button>
 | 
					    </button>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,199 +0,0 @@
 | 
				
			|||||||
'use client';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import { Cpu, Loader2, Search } from 'lucide-react';
 | 
					 | 
				
			||||||
import { cn } from '@/lib/utils';
 | 
					 | 
				
			||||||
import {
 | 
					 | 
				
			||||||
  Popover,
 | 
					 | 
				
			||||||
  PopoverButton,
 | 
					 | 
				
			||||||
  PopoverPanel,
 | 
					 | 
				
			||||||
  Transition,
 | 
					 | 
				
			||||||
} from '@headlessui/react';
 | 
					 | 
				
			||||||
import { Fragment, useEffect, useMemo, useState } from 'react';
 | 
					 | 
				
			||||||
import { MinimalProvider } from '@/lib/models/types';
 | 
					 | 
				
			||||||
import { useChat } from '@/lib/hooks/useChat';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const ModelSelector = () => {
 | 
					 | 
				
			||||||
  const [providers, setProviders] = useState<MinimalProvider[]>([]);
 | 
					 | 
				
			||||||
  const [isLoading, setIsLoading] = useState(true);
 | 
					 | 
				
			||||||
  const [searchQuery, setSearchQuery] = useState('');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const { setChatModelProvider, chatModelProvider } = useChat();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  useEffect(() => {
 | 
					 | 
				
			||||||
    const loadProviders = async () => {
 | 
					 | 
				
			||||||
      try {
 | 
					 | 
				
			||||||
        setIsLoading(true);
 | 
					 | 
				
			||||||
        const res = await fetch('/api/providers');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (!res.ok) {
 | 
					 | 
				
			||||||
          throw new Error('Failed to fetch providers');
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const data: { providers: MinimalProvider[] } = await res.json();
 | 
					 | 
				
			||||||
        setProviders(data.providers);
 | 
					 | 
				
			||||||
      } catch (error) {
 | 
					 | 
				
			||||||
        console.error('Error loading providers:', error);
 | 
					 | 
				
			||||||
      } finally {
 | 
					 | 
				
			||||||
        setIsLoading(false);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    loadProviders();
 | 
					 | 
				
			||||||
  }, []);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const orderedProviders = useMemo(() => {
 | 
					 | 
				
			||||||
    if (!chatModelProvider?.providerId) return providers;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const currentProviderIndex = providers.findIndex(
 | 
					 | 
				
			||||||
      (p) => p.id === chatModelProvider.providerId,
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (currentProviderIndex === -1) {
 | 
					 | 
				
			||||||
      return providers;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const selectedProvider = providers[currentProviderIndex];
 | 
					 | 
				
			||||||
    const remainingProviders = providers.filter(
 | 
					 | 
				
			||||||
      (_, index) => index !== currentProviderIndex,
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return [selectedProvider, ...remainingProviders];
 | 
					 | 
				
			||||||
  }, [providers, chatModelProvider]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const handleModelSelect = (providerId: string, modelKey: string) => {
 | 
					 | 
				
			||||||
    setChatModelProvider({ providerId, key: modelKey });
 | 
					 | 
				
			||||||
    localStorage.setItem('chatModelProviderId', providerId);
 | 
					 | 
				
			||||||
    localStorage.setItem('chatModelKey', modelKey);
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const filteredProviders = orderedProviders
 | 
					 | 
				
			||||||
    .map((provider) => ({
 | 
					 | 
				
			||||||
      ...provider,
 | 
					 | 
				
			||||||
      chatModels: provider.chatModels.filter(
 | 
					 | 
				
			||||||
        (model) =>
 | 
					 | 
				
			||||||
          model.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
 | 
					 | 
				
			||||||
          provider.name.toLowerCase().includes(searchQuery.toLowerCase()),
 | 
					 | 
				
			||||||
      ),
 | 
					 | 
				
			||||||
    }))
 | 
					 | 
				
			||||||
    .filter((provider) => provider.chatModels.length > 0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return (
 | 
					 | 
				
			||||||
    <Popover className="relative w-full max-w-[15rem] md:max-w-md lg:max-w-lg">
 | 
					 | 
				
			||||||
      <PopoverButton
 | 
					 | 
				
			||||||
        type="button"
 | 
					 | 
				
			||||||
        className="active:border-none hover:bg-light-200  hover:dark:bg-dark-200 p-2 rounded-lg focus:outline-none headless-open:text-black dark:headless-open:text-white text-black/50 dark:text-white/50 active:scale-95 transition duration-200 hover:text-black dark:hover:text-white"
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        <Cpu size={16} className="text-sky-500" />
 | 
					 | 
				
			||||||
      </PopoverButton>
 | 
					 | 
				
			||||||
      <Transition
 | 
					 | 
				
			||||||
        as={Fragment}
 | 
					 | 
				
			||||||
        enter="transition ease-out duration-100"
 | 
					 | 
				
			||||||
        enterFrom="opacity-0 translate-y-1"
 | 
					 | 
				
			||||||
        enterTo="opacity-100 translate-y-0"
 | 
					 | 
				
			||||||
        leave="transition ease-in duration-100"
 | 
					 | 
				
			||||||
        leaveFrom="opacity-100 translate-y-0"
 | 
					 | 
				
			||||||
        leaveTo="opacity-0 translate-y-1"
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        <PopoverPanel className="absolute z-10 w-[230px] sm:w-[270px] md:w-[300px] -right-4">
 | 
					 | 
				
			||||||
          <div className="bg-light-primary dark:bg-dark-primary max-h-[300px] sm:max-w-none border rounded-lg border-light-200 dark:border-dark-200 w-full flex flex-col shadow-lg overflow-hidden">
 | 
					 | 
				
			||||||
            <div className="p-4 border-b border-light-200 dark:border-dark-200">
 | 
					 | 
				
			||||||
              <div className="relative">
 | 
					 | 
				
			||||||
                <Search
 | 
					 | 
				
			||||||
                  size={16}
 | 
					 | 
				
			||||||
                  className="absolute left-3 top-1/2 -translate-y-1/2 text-black/40 dark:text-white/40"
 | 
					 | 
				
			||||||
                />
 | 
					 | 
				
			||||||
                <input
 | 
					 | 
				
			||||||
                  type="text"
 | 
					 | 
				
			||||||
                  placeholder="Search models..."
 | 
					 | 
				
			||||||
                  value={searchQuery}
 | 
					 | 
				
			||||||
                  onChange={(e) => setSearchQuery(e.target.value)}
 | 
					 | 
				
			||||||
                  className="w-full pl-9 pr-3 py-2 bg-light-secondary dark:bg-dark-secondary rounded-lg placeholder:text-sm text-sm text-black dark:text-white placeholder:text-black/40 dark:placeholder:text-white/40 focus:outline-none focus:ring-2 focus:ring-sky-500/20 border border-transparent focus:border-sky-500/30 transition duration-200"
 | 
					 | 
				
			||||||
                />
 | 
					 | 
				
			||||||
              </div>
 | 
					 | 
				
			||||||
            </div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <div className="max-h-[320px] overflow-y-auto">
 | 
					 | 
				
			||||||
              {isLoading ? (
 | 
					 | 
				
			||||||
                <div className="flex items-center justify-center py-16">
 | 
					 | 
				
			||||||
                  <Loader2
 | 
					 | 
				
			||||||
                    className="animate-spin text-black/40 dark:text-white/40"
 | 
					 | 
				
			||||||
                    size={24}
 | 
					 | 
				
			||||||
                  />
 | 
					 | 
				
			||||||
                </div>
 | 
					 | 
				
			||||||
              ) : filteredProviders.length === 0 ? (
 | 
					 | 
				
			||||||
                <div className="text-center py-16 px-4 text-black/60 dark:text-white/60 text-sm">
 | 
					 | 
				
			||||||
                  {searchQuery
 | 
					 | 
				
			||||||
                    ? 'No models found'
 | 
					 | 
				
			||||||
                    : 'No chat models configured'}
 | 
					 | 
				
			||||||
                </div>
 | 
					 | 
				
			||||||
              ) : (
 | 
					 | 
				
			||||||
                <div className="flex flex-col">
 | 
					 | 
				
			||||||
                  {filteredProviders.map((provider, providerIndex) => (
 | 
					 | 
				
			||||||
                    <div key={provider.id}>
 | 
					 | 
				
			||||||
                      <div className="px-4 py-2.5 sticky top-0 bg-light-primary dark:bg-dark-primary border-b border-light-200/50 dark:border-dark-200/50">
 | 
					 | 
				
			||||||
                        <p className="text-xs text-black/50 dark:text-white/50 uppercase tracking-wider">
 | 
					 | 
				
			||||||
                          {provider.name}
 | 
					 | 
				
			||||||
                        </p>
 | 
					 | 
				
			||||||
                      </div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                      <div className="flex flex-col px-2 py-2 space-y-0.5">
 | 
					 | 
				
			||||||
                        {provider.chatModels.map((model) => (
 | 
					 | 
				
			||||||
                          <button
 | 
					 | 
				
			||||||
                            key={model.key}
 | 
					 | 
				
			||||||
                            onClick={() =>
 | 
					 | 
				
			||||||
                              handleModelSelect(provider.id, model.key)
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
                            type="button"
 | 
					 | 
				
			||||||
                            className={cn(
 | 
					 | 
				
			||||||
                              'px-3 py-2 flex items-center justify-between text-start duration-200 cursor-pointer transition rounded-lg group',
 | 
					 | 
				
			||||||
                              chatModelProvider?.providerId === provider.id &&
 | 
					 | 
				
			||||||
                                chatModelProvider?.key === model.key
 | 
					 | 
				
			||||||
                                ? 'bg-light-secondary dark:bg-dark-secondary'
 | 
					 | 
				
			||||||
                                : 'hover:bg-light-secondary dark:hover:bg-dark-secondary',
 | 
					 | 
				
			||||||
                            )}
 | 
					 | 
				
			||||||
                          >
 | 
					 | 
				
			||||||
                            <div className="flex items-center space-x-2.5 min-w-0 flex-1">
 | 
					 | 
				
			||||||
                              <Cpu
 | 
					 | 
				
			||||||
                                size={15}
 | 
					 | 
				
			||||||
                                className={cn(
 | 
					 | 
				
			||||||
                                  'shrink-0',
 | 
					 | 
				
			||||||
                                  chatModelProvider?.providerId ===
 | 
					 | 
				
			||||||
                                    provider.id &&
 | 
					 | 
				
			||||||
                                    chatModelProvider?.key === model.key
 | 
					 | 
				
			||||||
                                    ? 'text-sky-500'
 | 
					 | 
				
			||||||
                                    : 'text-black/50 dark:text-white/50 group-hover:text-black/70 group-hover:dark:text-white/70',
 | 
					 | 
				
			||||||
                                )}
 | 
					 | 
				
			||||||
                              />
 | 
					 | 
				
			||||||
                              <p
 | 
					 | 
				
			||||||
                                className={cn(
 | 
					 | 
				
			||||||
                                  'text-sm truncate',
 | 
					 | 
				
			||||||
                                  chatModelProvider?.providerId ===
 | 
					 | 
				
			||||||
                                    provider.id &&
 | 
					 | 
				
			||||||
                                    chatModelProvider?.key === model.key
 | 
					 | 
				
			||||||
                                    ? 'text-sky-500 font-medium'
 | 
					 | 
				
			||||||
                                    : 'text-black/70 dark:text-white/70 group-hover:text-black dark:group-hover:text-white',
 | 
					 | 
				
			||||||
                                )}
 | 
					 | 
				
			||||||
                              >
 | 
					 | 
				
			||||||
                                {model.name}
 | 
					 | 
				
			||||||
                              </p>
 | 
					 | 
				
			||||||
                            </div>
 | 
					 | 
				
			||||||
                          </button>
 | 
					 | 
				
			||||||
                        ))}
 | 
					 | 
				
			||||||
                      </div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                      {providerIndex < filteredProviders.length - 1 && (
 | 
					 | 
				
			||||||
                        <div className="h-px bg-light-200 dark:bg-dark-200" />
 | 
					 | 
				
			||||||
                      )}
 | 
					 | 
				
			||||||
                    </div>
 | 
					 | 
				
			||||||
                  ))}
 | 
					 | 
				
			||||||
                </div>
 | 
					 | 
				
			||||||
              )}
 | 
					 | 
				
			||||||
            </div>
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
        </PopoverPanel>
 | 
					 | 
				
			||||||
      </Transition>
 | 
					 | 
				
			||||||
    </Popover>
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default ModelSelector;
 | 
					 | 
				
			||||||
@@ -15,20 +15,19 @@ import {
 | 
				
			|||||||
} from '@headlessui/react';
 | 
					} from '@headlessui/react';
 | 
				
			||||||
import { SiReddit, SiYoutube } from '@icons-pack/react-simple-icons';
 | 
					import { SiReddit, SiYoutube } from '@icons-pack/react-simple-icons';
 | 
				
			||||||
import { Fragment } from 'react';
 | 
					import { Fragment } from 'react';
 | 
				
			||||||
import { useChat } from '@/lib/hooks/useChat';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const focusModes = [
 | 
					const focusModes = [
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    key: 'webSearch',
 | 
					    key: 'webSearch',
 | 
				
			||||||
    title: 'All',
 | 
					    title: 'All',
 | 
				
			||||||
    description: 'Searches across all of the internet',
 | 
					    description: 'Searches across all of the internet',
 | 
				
			||||||
    icon: <Globe size={16} />,
 | 
					    icon: <Globe size={20} />,
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    key: 'academicSearch',
 | 
					    key: 'academicSearch',
 | 
				
			||||||
    title: 'Academic',
 | 
					    title: 'Academic',
 | 
				
			||||||
    description: 'Search in published academic papers',
 | 
					    description: 'Search in published academic papers',
 | 
				
			||||||
    icon: <SwatchBook size={16} />,
 | 
					    icon: <SwatchBook size={20} />,
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    key: 'writingAssistant',
 | 
					    key: 'writingAssistant',
 | 
				
			||||||
@@ -40,38 +39,47 @@ const focusModes = [
 | 
				
			|||||||
    key: 'wolframAlphaSearch',
 | 
					    key: 'wolframAlphaSearch',
 | 
				
			||||||
    title: 'Wolfram Alpha',
 | 
					    title: 'Wolfram Alpha',
 | 
				
			||||||
    description: 'Computational knowledge engine',
 | 
					    description: 'Computational knowledge engine',
 | 
				
			||||||
    icon: <BadgePercent size={16} />,
 | 
					    icon: <BadgePercent size={20} />,
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    key: 'youtubeSearch',
 | 
					    key: 'youtubeSearch',
 | 
				
			||||||
    title: 'Youtube',
 | 
					    title: 'Youtube',
 | 
				
			||||||
    description: 'Search and watch videos',
 | 
					    description: 'Search and watch videos',
 | 
				
			||||||
    icon: <SiYoutube className="h-[16px] w-auto mr-0.5" />,
 | 
					    icon: <SiYoutube className="h-5 w-auto mr-0.5" />,
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    key: 'redditSearch',
 | 
					    key: 'redditSearch',
 | 
				
			||||||
    title: 'Reddit',
 | 
					    title: 'Reddit',
 | 
				
			||||||
    description: 'Search for discussions and opinions',
 | 
					    description: 'Search for discussions and opinions',
 | 
				
			||||||
    icon: <SiReddit className="h-[16px] w-auto mr-0.5" />,
 | 
					    icon: <SiReddit className="h-5 w-auto mr-0.5" />,
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Focus = () => {
 | 
					const Focus = ({
 | 
				
			||||||
  const { focusMode, setFocusMode } = useChat();
 | 
					  focusMode,
 | 
				
			||||||
 | 
					  setFocusMode,
 | 
				
			||||||
 | 
					}: {
 | 
				
			||||||
 | 
					  focusMode: string;
 | 
				
			||||||
 | 
					  setFocusMode: (mode: string) => void;
 | 
				
			||||||
 | 
					}) => {
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <Popover className="relative w-full max-w-[15rem] md:max-w-md lg:max-w-lg">
 | 
					    <Popover className="relative w-full max-w-[15rem] md:max-w-md lg:max-w-lg mt-[6.5px]">
 | 
				
			||||||
      <PopoverButton
 | 
					      <PopoverButton
 | 
				
			||||||
        type="button"
 | 
					        type="button"
 | 
				
			||||||
        className="active:border-none hover:bg-light-200 hover:dark:bg-dark-200 p-2 rounded-lg focus:outline-none headless-open:text-black dark:headless-open:text-white text-black/50 dark:text-white/50 active:scale-95 transition duration-200 hover:text-black dark:hover:text-white"
 | 
					        className=" text-black/50 dark:text-white/50 rounded-xl hover:bg-light-secondary dark:hover:bg-dark-secondary active:scale-95 transition duration-200 hover:text-black dark:hover:text-white"
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        {focusMode !== 'webSearch' ? (
 | 
					        {focusMode !== 'webSearch' ? (
 | 
				
			||||||
          <div className="flex flex-row items-center space-x-1">
 | 
					          <div className="flex flex-row items-center space-x-1">
 | 
				
			||||||
            {focusModes.find((mode) => mode.key === focusMode)?.icon}
 | 
					            {focusModes.find((mode) => mode.key === focusMode)?.icon}
 | 
				
			||||||
 | 
					            <p className="text-xs font-medium hidden lg:block">
 | 
				
			||||||
 | 
					              {focusModes.find((mode) => mode.key === focusMode)?.title}
 | 
				
			||||||
 | 
					            </p>
 | 
				
			||||||
 | 
					            <ChevronDown size={20} className="-translate-x-1" />
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        ) : (
 | 
					        ) : (
 | 
				
			||||||
          <div className="flex flex-row items-center space-x-1">
 | 
					          <div className="flex flex-row items-center space-x-1">
 | 
				
			||||||
            <Globe size={16} />
 | 
					            <ScanEye size={20} />
 | 
				
			||||||
 | 
					            <p className="text-xs font-medium hidden lg:block">Focus</p>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        )}
 | 
					        )}
 | 
				
			||||||
      </PopoverButton>
 | 
					      </PopoverButton>
 | 
				
			||||||
@@ -84,14 +92,14 @@ const Focus = () => {
 | 
				
			|||||||
        leaveFrom="opacity-100 translate-y-0"
 | 
					        leaveFrom="opacity-100 translate-y-0"
 | 
				
			||||||
        leaveTo="opacity-0 translate-y-1"
 | 
					        leaveTo="opacity-0 translate-y-1"
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        <PopoverPanel className="absolute z-10 w-64 md:w-[500px] -right-4">
 | 
					        <PopoverPanel className="absolute z-10 w-64 md:w-[500px] left-0">
 | 
				
			||||||
          <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2 bg-light-primary dark:bg-dark-primary border rounded-lg border-light-200 dark:border-dark-200 w-full p-4 max-h-[200px] md:max-h-none overflow-y-auto">
 | 
					          <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2 bg-light-primary dark:bg-dark-primary border rounded-lg border-light-200 dark:border-dark-200 w-full p-4 max-h-[200px] md:max-h-none overflow-y-auto">
 | 
				
			||||||
            {focusModes.map((mode, i) => (
 | 
					            {focusModes.map((mode, i) => (
 | 
				
			||||||
              <PopoverButton
 | 
					              <PopoverButton
 | 
				
			||||||
                onClick={() => setFocusMode(mode.key)}
 | 
					                onClick={() => setFocusMode(mode.key)}
 | 
				
			||||||
                key={i}
 | 
					                key={i}
 | 
				
			||||||
                className={cn(
 | 
					                className={cn(
 | 
				
			||||||
                  'p-2 rounded-lg flex flex-col items-start justify-start text-start space-y-2 duration-200 cursor-pointer transition focus:outline-none',
 | 
					                  'p-2 rounded-lg flex flex-col items-start justify-start text-start space-y-2 duration-200 cursor-pointer transition',
 | 
				
			||||||
                  focusMode === mode.key
 | 
					                  focusMode === mode.key
 | 
				
			||||||
                    ? 'bg-light-secondary dark:bg-dark-secondary'
 | 
					                    ? 'bg-light-secondary dark:bg-dark-secondary'
 | 
				
			||||||
                    : 'hover:bg-light-secondary dark:hover:bg-dark-secondary',
 | 
					                    : 'hover:bg-light-secondary dark:hover:bg-dark-secondary',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,20 +7,19 @@ import {
 | 
				
			|||||||
  Transition,
 | 
					  Transition,
 | 
				
			||||||
} from '@headlessui/react';
 | 
					} from '@headlessui/react';
 | 
				
			||||||
import { Fragment } from 'react';
 | 
					import { Fragment } from 'react';
 | 
				
			||||||
import { useChat } from '@/lib/hooks/useChat';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const OptimizationModes = [
 | 
					const OptimizationModes = [
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    key: 'speed',
 | 
					    key: 'speed',
 | 
				
			||||||
    title: 'Speed',
 | 
					    title: 'Speed',
 | 
				
			||||||
    description: 'Prioritize speed and get the quickest possible answer.',
 | 
					    description: 'Prioritize speed and get the quickest possible answer.',
 | 
				
			||||||
    icon: <Zap size={16} className="text-[#FF9800]" />,
 | 
					    icon: <Zap size={20} className="text-[#FF9800]" />,
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    key: 'balanced',
 | 
					    key: 'balanced',
 | 
				
			||||||
    title: 'Balanced',
 | 
					    title: 'Balanced',
 | 
				
			||||||
    description: 'Find the right balance between speed and accuracy',
 | 
					    description: 'Find the right balance between speed and accuracy',
 | 
				
			||||||
    icon: <Sliders size={16} className="text-[#4CAF50]" />,
 | 
					    icon: <Sliders size={20} className="text-[#4CAF50]" />,
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    key: 'quality',
 | 
					    key: 'quality',
 | 
				
			||||||
@@ -35,69 +34,69 @@ const OptimizationModes = [
 | 
				
			|||||||
  },
 | 
					  },
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Optimization = () => {
 | 
					const Optimization = ({
 | 
				
			||||||
  const { optimizationMode, setOptimizationMode } = useChat();
 | 
					  optimizationMode,
 | 
				
			||||||
 | 
					  setOptimizationMode,
 | 
				
			||||||
 | 
					}: {
 | 
				
			||||||
 | 
					  optimizationMode: string;
 | 
				
			||||||
 | 
					  setOptimizationMode: (mode: string) => void;
 | 
				
			||||||
 | 
					}) => {
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <Popover className="relative w-full max-w-[15rem] md:max-w-md lg:max-w-lg">
 | 
					    <Popover className="relative w-full max-w-[15rem] md:max-w-md lg:max-w-lg">
 | 
				
			||||||
      {({ open }) => (
 | 
					      <PopoverButton
 | 
				
			||||||
        <>
 | 
					        type="button"
 | 
				
			||||||
          <PopoverButton
 | 
					        className="p-2 text-black/50 dark:text-white/50 rounded-xl hover:bg-light-secondary dark:hover:bg-dark-secondary active:scale-95 transition duration-200 hover:text-black dark:hover:text-white"
 | 
				
			||||||
            type="button"
 | 
					      >
 | 
				
			||||||
            className="p-2 text-black/50 dark:text-white/50 rounded-xl hover:bg-light-secondary dark:hover:bg-dark-secondary active:scale-95 transition duration-200 hover:text-black dark:hover:text-white focus:outline-none"
 | 
					        <div className="flex flex-row items-center space-x-1">
 | 
				
			||||||
          >
 | 
					          {
 | 
				
			||||||
            <div className="flex flex-row items-center space-x-1">
 | 
					            OptimizationModes.find((mode) => mode.key === optimizationMode)
 | 
				
			||||||
              {
 | 
					              ?.icon
 | 
				
			||||||
                OptimizationModes.find((mode) => mode.key === optimizationMode)
 | 
					          }
 | 
				
			||||||
                  ?.icon
 | 
					          <p className="text-xs font-medium">
 | 
				
			||||||
              }
 | 
					            {
 | 
				
			||||||
              <ChevronDown
 | 
					              OptimizationModes.find((mode) => mode.key === optimizationMode)
 | 
				
			||||||
                size={16}
 | 
					                ?.title
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          </p>
 | 
				
			||||||
 | 
					          <ChevronDown size={20} />
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </PopoverButton>
 | 
				
			||||||
 | 
					      <Transition
 | 
				
			||||||
 | 
					        as={Fragment}
 | 
				
			||||||
 | 
					        enter="transition ease-out duration-150"
 | 
				
			||||||
 | 
					        enterFrom="opacity-0 translate-y-1"
 | 
				
			||||||
 | 
					        enterTo="opacity-100 translate-y-0"
 | 
				
			||||||
 | 
					        leave="transition ease-in duration-150"
 | 
				
			||||||
 | 
					        leaveFrom="opacity-100 translate-y-0"
 | 
				
			||||||
 | 
					        leaveTo="opacity-0 translate-y-1"
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        <PopoverPanel className="absolute z-10 w-64 md:w-[250px] right-0">
 | 
				
			||||||
 | 
					          <div className="flex flex-col gap-2 bg-light-primary dark:bg-dark-primary border rounded-lg border-light-200 dark:border-dark-200 w-full p-4 max-h-[200px] md:max-h-none overflow-y-auto">
 | 
				
			||||||
 | 
					            {OptimizationModes.map((mode, i) => (
 | 
				
			||||||
 | 
					              <PopoverButton
 | 
				
			||||||
 | 
					                onClick={() => setOptimizationMode(mode.key)}
 | 
				
			||||||
 | 
					                key={i}
 | 
				
			||||||
 | 
					                disabled={mode.key === 'quality'}
 | 
				
			||||||
                className={cn(
 | 
					                className={cn(
 | 
				
			||||||
                  open ? 'rotate-180' : 'rotate-0',
 | 
					                  'p-2 rounded-lg flex flex-col items-start justify-start text-start space-y-1 duration-200 cursor-pointer transition',
 | 
				
			||||||
                  'transition duration:200',
 | 
					                  optimizationMode === mode.key
 | 
				
			||||||
 | 
					                    ? 'bg-light-secondary dark:bg-dark-secondary'
 | 
				
			||||||
 | 
					                    : 'hover:bg-light-secondary dark:hover:bg-dark-secondary',
 | 
				
			||||||
 | 
					                  mode.key === 'quality' && 'opacity-50 cursor-not-allowed',
 | 
				
			||||||
                )}
 | 
					                )}
 | 
				
			||||||
              />
 | 
					              >
 | 
				
			||||||
            </div>
 | 
					                <div className="flex flex-row items-center space-x-1 text-black dark:text-white">
 | 
				
			||||||
          </PopoverButton>
 | 
					                  {mode.icon}
 | 
				
			||||||
          <Transition
 | 
					                  <p className="text-sm font-medium">{mode.title}</p>
 | 
				
			||||||
            as={Fragment}
 | 
					                </div>
 | 
				
			||||||
            enter="transition ease-out duration-150"
 | 
					                <p className="text-black/70 dark:text-white/70 text-xs">
 | 
				
			||||||
            enterFrom="opacity-0 translate-y-1"
 | 
					                  {mode.description}
 | 
				
			||||||
            enterTo="opacity-100 translate-y-0"
 | 
					                </p>
 | 
				
			||||||
            leave="transition ease-in duration-150"
 | 
					              </PopoverButton>
 | 
				
			||||||
            leaveFrom="opacity-100 translate-y-0"
 | 
					            ))}
 | 
				
			||||||
            leaveTo="opacity-0 translate-y-1"
 | 
					          </div>
 | 
				
			||||||
          >
 | 
					        </PopoverPanel>
 | 
				
			||||||
            <PopoverPanel className="absolute z-10 w-64 md:w-[250px] left-0">
 | 
					      </Transition>
 | 
				
			||||||
              <div className="flex flex-col gap-2 bg-light-primary dark:bg-dark-primary border rounded-lg border-light-200 dark:border-dark-200 w-full p-4 max-h-[200px] md:max-h-none overflow-y-auto">
 | 
					 | 
				
			||||||
                {OptimizationModes.map((mode, i) => (
 | 
					 | 
				
			||||||
                  <PopoverButton
 | 
					 | 
				
			||||||
                    onClick={() => setOptimizationMode(mode.key)}
 | 
					 | 
				
			||||||
                    key={i}
 | 
					 | 
				
			||||||
                    disabled={mode.key === 'quality'}
 | 
					 | 
				
			||||||
                    className={cn(
 | 
					 | 
				
			||||||
                      'p-2 rounded-lg flex flex-col items-start justify-start text-start space-y-1 duration-200 cursor-pointer transition focus:outline-none',
 | 
					 | 
				
			||||||
                      optimizationMode === mode.key
 | 
					 | 
				
			||||||
                        ? 'bg-light-secondary dark:bg-dark-secondary'
 | 
					 | 
				
			||||||
                        : 'hover:bg-light-secondary dark:hover:bg-dark-secondary',
 | 
					 | 
				
			||||||
                      mode.key === 'quality' && 'opacity-50 cursor-not-allowed',
 | 
					 | 
				
			||||||
                    )}
 | 
					 | 
				
			||||||
                  >
 | 
					 | 
				
			||||||
                    <div className="flex flex-row items-center space-x-1 text-black dark:text-white">
 | 
					 | 
				
			||||||
                      {mode.icon}
 | 
					 | 
				
			||||||
                      <p className="text-sm font-medium">{mode.title}</p>
 | 
					 | 
				
			||||||
                    </div>
 | 
					 | 
				
			||||||
                    <p className="text-black/70 dark:text-white/70 text-xs">
 | 
					 | 
				
			||||||
                      {mode.description}
 | 
					 | 
				
			||||||
                    </p>
 | 
					 | 
				
			||||||
                  </PopoverButton>
 | 
					 | 
				
			||||||
                ))}
 | 
					 | 
				
			||||||
              </div>
 | 
					 | 
				
			||||||
            </PopoverPanel>
 | 
					 | 
				
			||||||
          </Transition>
 | 
					 | 
				
			||||||
        </>
 | 
					 | 
				
			||||||
      )}
 | 
					 | 
				
			||||||
    </Popover>
 | 
					    </Popover>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,223 +1,40 @@
 | 
				
			|||||||
import { Clock, Edit, Share, Trash, FileText, FileDown } from 'lucide-react';
 | 
					import { Clock, Edit, Share, Trash } from 'lucide-react';
 | 
				
			||||||
import { Message } from './ChatWindow';
 | 
					import { Message } from './ChatWindow';
 | 
				
			||||||
import { useEffect, useState, Fragment } from 'react';
 | 
					import { useEffect, useState } from 'react';
 | 
				
			||||||
import { formatTimeDifference } from '@/lib/utils';
 | 
					import { formatTimeDifference } from '@/lib/utils';
 | 
				
			||||||
import DeleteChat from './DeleteChat';
 | 
					import DeleteChat from './DeleteChat';
 | 
				
			||||||
import {
 | 
					 | 
				
			||||||
  Popover,
 | 
					 | 
				
			||||||
  PopoverButton,
 | 
					 | 
				
			||||||
  PopoverPanel,
 | 
					 | 
				
			||||||
  Transition,
 | 
					 | 
				
			||||||
} from '@headlessui/react';
 | 
					 | 
				
			||||||
import jsPDF from 'jspdf';
 | 
					 | 
				
			||||||
import { useChat, Section } from '@/lib/hooks/useChat';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const downloadFile = (filename: string, content: string, type: string) => {
 | 
					const Navbar = ({
 | 
				
			||||||
  const blob = new Blob([content], { type });
 | 
					  chatId,
 | 
				
			||||||
  const url = URL.createObjectURL(blob);
 | 
					  messages,
 | 
				
			||||||
  const a = document.createElement('a');
 | 
					}: {
 | 
				
			||||||
  a.href = url;
 | 
					  messages: Message[];
 | 
				
			||||||
  a.download = filename;
 | 
					  chatId: string;
 | 
				
			||||||
  document.body.appendChild(a);
 | 
					}) => {
 | 
				
			||||||
  a.click();
 | 
					 | 
				
			||||||
  setTimeout(() => {
 | 
					 | 
				
			||||||
    document.body.removeChild(a);
 | 
					 | 
				
			||||||
    URL.revokeObjectURL(url);
 | 
					 | 
				
			||||||
  }, 0);
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const exportAsMarkdown = (sections: Section[], title: string) => {
 | 
					 | 
				
			||||||
  const date = new Date(
 | 
					 | 
				
			||||||
    sections[0]?.userMessage?.createdAt || Date.now(),
 | 
					 | 
				
			||||||
  ).toLocaleString();
 | 
					 | 
				
			||||||
  let md = `# 💬 Chat Export: ${title}\n\n`;
 | 
					 | 
				
			||||||
  md += `*Exported on: ${date}*\n\n---\n`;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  sections.forEach((section, idx) => {
 | 
					 | 
				
			||||||
    if (section.userMessage) {
 | 
					 | 
				
			||||||
      md += `\n---\n`;
 | 
					 | 
				
			||||||
      md += `**🧑 User**  
 | 
					 | 
				
			||||||
`;
 | 
					 | 
				
			||||||
      md += `*${new Date(section.userMessage.createdAt).toLocaleString()}*\n\n`;
 | 
					 | 
				
			||||||
      md += `> ${section.userMessage.content.replace(/\n/g, '\n> ')}\n`;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (section.assistantMessage) {
 | 
					 | 
				
			||||||
      md += `\n---\n`;
 | 
					 | 
				
			||||||
      md += `**🤖 Assistant**  
 | 
					 | 
				
			||||||
`;
 | 
					 | 
				
			||||||
      md += `*${new Date(section.assistantMessage.createdAt).toLocaleString()}*\n\n`;
 | 
					 | 
				
			||||||
      md += `> ${section.assistantMessage.content.replace(/\n/g, '\n> ')}\n`;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (
 | 
					 | 
				
			||||||
      section.sourceMessage &&
 | 
					 | 
				
			||||||
      section.sourceMessage.sources &&
 | 
					 | 
				
			||||||
      section.sourceMessage.sources.length > 0
 | 
					 | 
				
			||||||
    ) {
 | 
					 | 
				
			||||||
      md += `\n**Citations:**\n`;
 | 
					 | 
				
			||||||
      section.sourceMessage.sources.forEach((src: any, i: number) => {
 | 
					 | 
				
			||||||
        const url = src.metadata?.url || '';
 | 
					 | 
				
			||||||
        md += `- [${i + 1}] [${url}](${url})\n`;
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
  md += '\n---\n';
 | 
					 | 
				
			||||||
  downloadFile(`${title || 'chat'}.md`, md, 'text/markdown');
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const exportAsPDF = (sections: Section[], title: string) => {
 | 
					 | 
				
			||||||
  const doc = new jsPDF();
 | 
					 | 
				
			||||||
  const date = new Date(
 | 
					 | 
				
			||||||
    sections[0]?.userMessage?.createdAt || Date.now(),
 | 
					 | 
				
			||||||
  ).toLocaleString();
 | 
					 | 
				
			||||||
  let y = 15;
 | 
					 | 
				
			||||||
  const pageHeight = doc.internal.pageSize.height;
 | 
					 | 
				
			||||||
  doc.setFontSize(18);
 | 
					 | 
				
			||||||
  doc.text(`Chat Export: ${title}`, 10, y);
 | 
					 | 
				
			||||||
  y += 8;
 | 
					 | 
				
			||||||
  doc.setFontSize(11);
 | 
					 | 
				
			||||||
  doc.setTextColor(100);
 | 
					 | 
				
			||||||
  doc.text(`Exported on: ${date}`, 10, y);
 | 
					 | 
				
			||||||
  y += 8;
 | 
					 | 
				
			||||||
  doc.setDrawColor(200);
 | 
					 | 
				
			||||||
  doc.line(10, y, 200, y);
 | 
					 | 
				
			||||||
  y += 6;
 | 
					 | 
				
			||||||
  doc.setTextColor(30);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  sections.forEach((section, idx) => {
 | 
					 | 
				
			||||||
    if (section.userMessage) {
 | 
					 | 
				
			||||||
      if (y > pageHeight - 30) {
 | 
					 | 
				
			||||||
        doc.addPage();
 | 
					 | 
				
			||||||
        y = 15;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      doc.setFont('helvetica', 'bold');
 | 
					 | 
				
			||||||
      doc.text('User', 10, y);
 | 
					 | 
				
			||||||
      doc.setFont('helvetica', 'normal');
 | 
					 | 
				
			||||||
      doc.setFontSize(10);
 | 
					 | 
				
			||||||
      doc.setTextColor(120);
 | 
					 | 
				
			||||||
      doc.text(
 | 
					 | 
				
			||||||
        `${new Date(section.userMessage.createdAt).toLocaleString()}`,
 | 
					 | 
				
			||||||
        40,
 | 
					 | 
				
			||||||
        y,
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
      y += 6;
 | 
					 | 
				
			||||||
      doc.setTextColor(30);
 | 
					 | 
				
			||||||
      doc.setFontSize(12);
 | 
					 | 
				
			||||||
      const userLines = doc.splitTextToSize(section.userMessage.content, 180);
 | 
					 | 
				
			||||||
      for (let i = 0; i < userLines.length; i++) {
 | 
					 | 
				
			||||||
        if (y > pageHeight - 20) {
 | 
					 | 
				
			||||||
          doc.addPage();
 | 
					 | 
				
			||||||
          y = 15;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        doc.text(userLines[i], 12, y);
 | 
					 | 
				
			||||||
        y += 6;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      y += 6;
 | 
					 | 
				
			||||||
      doc.setDrawColor(230);
 | 
					 | 
				
			||||||
      if (y > pageHeight - 10) {
 | 
					 | 
				
			||||||
        doc.addPage();
 | 
					 | 
				
			||||||
        y = 15;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      doc.line(10, y, 200, y);
 | 
					 | 
				
			||||||
      y += 4;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (section.assistantMessage) {
 | 
					 | 
				
			||||||
      if (y > pageHeight - 30) {
 | 
					 | 
				
			||||||
        doc.addPage();
 | 
					 | 
				
			||||||
        y = 15;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      doc.setFont('helvetica', 'bold');
 | 
					 | 
				
			||||||
      doc.text('Assistant', 10, y);
 | 
					 | 
				
			||||||
      doc.setFont('helvetica', 'normal');
 | 
					 | 
				
			||||||
      doc.setFontSize(10);
 | 
					 | 
				
			||||||
      doc.setTextColor(120);
 | 
					 | 
				
			||||||
      doc.text(
 | 
					 | 
				
			||||||
        `${new Date(section.assistantMessage.createdAt).toLocaleString()}`,
 | 
					 | 
				
			||||||
        40,
 | 
					 | 
				
			||||||
        y,
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
      y += 6;
 | 
					 | 
				
			||||||
      doc.setTextColor(30);
 | 
					 | 
				
			||||||
      doc.setFontSize(12);
 | 
					 | 
				
			||||||
      const assistantLines = doc.splitTextToSize(
 | 
					 | 
				
			||||||
        section.assistantMessage.content,
 | 
					 | 
				
			||||||
        180,
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
      for (let i = 0; i < assistantLines.length; i++) {
 | 
					 | 
				
			||||||
        if (y > pageHeight - 20) {
 | 
					 | 
				
			||||||
          doc.addPage();
 | 
					 | 
				
			||||||
          y = 15;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        doc.text(assistantLines[i], 12, y);
 | 
					 | 
				
			||||||
        y += 6;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if (
 | 
					 | 
				
			||||||
        section.sourceMessage &&
 | 
					 | 
				
			||||||
        section.sourceMessage.sources &&
 | 
					 | 
				
			||||||
        section.sourceMessage.sources.length > 0
 | 
					 | 
				
			||||||
      ) {
 | 
					 | 
				
			||||||
        doc.setFontSize(11);
 | 
					 | 
				
			||||||
        doc.setTextColor(80);
 | 
					 | 
				
			||||||
        if (y > pageHeight - 20) {
 | 
					 | 
				
			||||||
          doc.addPage();
 | 
					 | 
				
			||||||
          y = 15;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        doc.text('Citations:', 12, y);
 | 
					 | 
				
			||||||
        y += 5;
 | 
					 | 
				
			||||||
        section.sourceMessage.sources.forEach((src: any, i: number) => {
 | 
					 | 
				
			||||||
          const url = src.metadata?.url || '';
 | 
					 | 
				
			||||||
          if (y > pageHeight - 15) {
 | 
					 | 
				
			||||||
            doc.addPage();
 | 
					 | 
				
			||||||
            y = 15;
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
          doc.text(`- [${i + 1}] ${url}`, 15, y);
 | 
					 | 
				
			||||||
          y += 5;
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
        doc.setTextColor(30);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      y += 6;
 | 
					 | 
				
			||||||
      doc.setDrawColor(230);
 | 
					 | 
				
			||||||
      if (y > pageHeight - 10) {
 | 
					 | 
				
			||||||
        doc.addPage();
 | 
					 | 
				
			||||||
        y = 15;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      doc.line(10, y, 200, y);
 | 
					 | 
				
			||||||
      y += 4;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
  doc.save(`${title || 'chat'}.pdf`);
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const Navbar = () => {
 | 
					 | 
				
			||||||
  const [title, setTitle] = useState<string>('');
 | 
					  const [title, setTitle] = useState<string>('');
 | 
				
			||||||
  const [timeAgo, setTimeAgo] = useState<string>('');
 | 
					  const [timeAgo, setTimeAgo] = useState<string>('');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const { sections, chatId } = useChat();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
    if (sections.length > 0 && sections[0].userMessage) {
 | 
					    if (messages.length > 0) {
 | 
				
			||||||
      const newTitle =
 | 
					      const newTitle =
 | 
				
			||||||
        sections[0].userMessage.content.length > 20
 | 
					        messages[0].content.length > 20
 | 
				
			||||||
          ? `${sections[0].userMessage.content.substring(0, 20).trim()}...`
 | 
					          ? `${messages[0].content.substring(0, 20).trim()}...`
 | 
				
			||||||
          : sections[0].userMessage.content;
 | 
					          : messages[0].content;
 | 
				
			||||||
      setTitle(newTitle);
 | 
					      setTitle(newTitle);
 | 
				
			||||||
      const newTimeAgo = formatTimeDifference(
 | 
					      const newTimeAgo = formatTimeDifference(
 | 
				
			||||||
        new Date(),
 | 
					        new Date(),
 | 
				
			||||||
        sections[0].userMessage.createdAt,
 | 
					        messages[0].createdAt,
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
      setTimeAgo(newTimeAgo);
 | 
					      setTimeAgo(newTimeAgo);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }, [sections]);
 | 
					  }, [messages]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
    const intervalId = setInterval(() => {
 | 
					    const intervalId = setInterval(() => {
 | 
				
			||||||
      if (sections.length > 0 && sections[0].userMessage) {
 | 
					      if (messages.length > 0) {
 | 
				
			||||||
        const newTimeAgo = formatTimeDifference(
 | 
					        const newTimeAgo = formatTimeDifference(
 | 
				
			||||||
          new Date(),
 | 
					          new Date(),
 | 
				
			||||||
          sections[0].userMessage.createdAt,
 | 
					          messages[0].createdAt,
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        setTimeAgo(newTimeAgo);
 | 
					        setTimeAgo(newTimeAgo);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@@ -228,91 +45,25 @@ const Navbar = () => {
 | 
				
			|||||||
  }, []);
 | 
					  }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <div className="sticky -mx-4 lg:mx-0 top-0 z-40 bg-light-primary/95 dark:bg-dark-primary/95 backdrop-blur-sm border-b border-light-200/50 dark:border-dark-200/30">
 | 
					    <div className="fixed z-40 top-0 left-0 right-0 px-4 lg:pl-[104px] lg:pr-6 lg:px-8 flex flex-row items-center justify-between w-full py-4 text-sm text-black dark:text-white/70 border-b bg-light-primary dark:bg-dark-primary border-light-100 dark:border-dark-200">
 | 
				
			||||||
      <div className="px-4 lg:px-6 py-4">
 | 
					      <a
 | 
				
			||||||
        <div className="flex items-center justify-between">
 | 
					        href="/"
 | 
				
			||||||
          <div className="flex items-center min-w-0">
 | 
					        className="active:scale-95 transition duration-100 cursor-pointer lg:hidden"
 | 
				
			||||||
            <a
 | 
					      >
 | 
				
			||||||
              href="/"
 | 
					        <Edit size={17} />
 | 
				
			||||||
              className="lg:hidden mr-3 p-2 -ml-2 rounded-lg hover:bg-light-secondary dark:hover:bg-dark-secondary transition-colors duration-200"
 | 
					      </a>
 | 
				
			||||||
            >
 | 
					      <div className="hidden lg:flex flex-row items-center justify-center space-x-2">
 | 
				
			||||||
              <Edit size={18} className="text-black/70 dark:text-white/70" />
 | 
					        <Clock size={17} />
 | 
				
			||||||
            </a>
 | 
					        <p className="text-xs">{timeAgo} ago</p>
 | 
				
			||||||
            <div className="hidden lg:flex items-center gap-2 text-black/50 dark:text-white/50 min-w-0">
 | 
					      </div>
 | 
				
			||||||
              <Clock size={14} />
 | 
					      <p className="hidden lg:flex">{title}</p>
 | 
				
			||||||
              <span className="text-xs whitespace-nowrap">{timeAgo} ago</span>
 | 
					 | 
				
			||||||
            </div>
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
          <div className="flex-1 mx-4 min-w-0">
 | 
					      <div className="flex flex-row items-center space-x-4">
 | 
				
			||||||
            <h1 className="text-center text-sm font-medium text-black/80 dark:text-white/90 truncate">
 | 
					        <Share
 | 
				
			||||||
              {title || 'New Conversation'}
 | 
					          size={17}
 | 
				
			||||||
            </h1>
 | 
					          className="active:scale-95 transition duration-100 cursor-pointer"
 | 
				
			||||||
          </div>
 | 
					        />
 | 
				
			||||||
 | 
					        <DeleteChat redirect chatId={chatId} chats={[]} setChats={() => {}} />
 | 
				
			||||||
          <div className="flex items-center gap-1 min-w-0">
 | 
					 | 
				
			||||||
            <Popover className="relative">
 | 
					 | 
				
			||||||
              <PopoverButton className="p-2 rounded-lg hover:bg-light-secondary dark:hover:bg-dark-secondary transition-colors duration-200">
 | 
					 | 
				
			||||||
                <Share size={16} className="text-black/60 dark:text-white/60" />
 | 
					 | 
				
			||||||
              </PopoverButton>
 | 
					 | 
				
			||||||
              <Transition
 | 
					 | 
				
			||||||
                as={Fragment}
 | 
					 | 
				
			||||||
                enter="transition ease-out duration-200"
 | 
					 | 
				
			||||||
                enterFrom="opacity-0 translate-y-1"
 | 
					 | 
				
			||||||
                enterTo="opacity-100 translate-y-0"
 | 
					 | 
				
			||||||
                leave="transition ease-in duration-150"
 | 
					 | 
				
			||||||
                leaveFrom="opacity-100 translate-y-0"
 | 
					 | 
				
			||||||
                leaveTo="opacity-0 translate-y-1"
 | 
					 | 
				
			||||||
              >
 | 
					 | 
				
			||||||
                <PopoverPanel className="absolute right-0 mt-2 w-64 origin-top-right rounded-2xl bg-light-primary dark:bg-dark-primary border border-light-200 dark:border-dark-200 shadow-xl shadow-black/10 dark:shadow-black/30 z-50">
 | 
					 | 
				
			||||||
                  <div className="p-3">
 | 
					 | 
				
			||||||
                    <div className="mb-2">
 | 
					 | 
				
			||||||
                      <p className="text-xs font-medium text-black/40 dark:text-white/40 uppercase tracking-wide">
 | 
					 | 
				
			||||||
                        Export Chat
 | 
					 | 
				
			||||||
                      </p>
 | 
					 | 
				
			||||||
                    </div>
 | 
					 | 
				
			||||||
                    <div className="space-y-1">
 | 
					 | 
				
			||||||
                      <button
 | 
					 | 
				
			||||||
                        className="w-full flex items-center gap-3 px-3 py-2 text-left rounded-xl hover:bg-light-secondary dark:hover:bg-dark-secondary transition-colors duration-200"
 | 
					 | 
				
			||||||
                        onClick={() => exportAsMarkdown(sections, title || '')}
 | 
					 | 
				
			||||||
                      >
 | 
					 | 
				
			||||||
                        <FileText size={16} className="text-[#24A0ED]" />
 | 
					 | 
				
			||||||
                        <div>
 | 
					 | 
				
			||||||
                          <p className="text-sm font-medium text-black dark:text-white">
 | 
					 | 
				
			||||||
                            Markdown
 | 
					 | 
				
			||||||
                          </p>
 | 
					 | 
				
			||||||
                          <p className="text-xs text-black/50 dark:text-white/50">
 | 
					 | 
				
			||||||
                            .md format
 | 
					 | 
				
			||||||
                          </p>
 | 
					 | 
				
			||||||
                        </div>
 | 
					 | 
				
			||||||
                      </button>
 | 
					 | 
				
			||||||
                      <button
 | 
					 | 
				
			||||||
                        className="w-full flex items-center gap-3 px-3 py-2 text-left rounded-xl hover:bg-light-secondary dark:hover:bg-dark-secondary transition-colors duration-200"
 | 
					 | 
				
			||||||
                        onClick={() => exportAsPDF(sections, title || '')}
 | 
					 | 
				
			||||||
                      >
 | 
					 | 
				
			||||||
                        <FileDown size={16} className="text-[#24A0ED]" />
 | 
					 | 
				
			||||||
                        <div>
 | 
					 | 
				
			||||||
                          <p className="text-sm font-medium text-black dark:text-white">
 | 
					 | 
				
			||||||
                            PDF
 | 
					 | 
				
			||||||
                          </p>
 | 
					 | 
				
			||||||
                          <p className="text-xs text-black/50 dark:text-white/50">
 | 
					 | 
				
			||||||
                            Document format
 | 
					 | 
				
			||||||
                          </p>
 | 
					 | 
				
			||||||
                        </div>
 | 
					 | 
				
			||||||
                      </button>
 | 
					 | 
				
			||||||
                    </div>
 | 
					 | 
				
			||||||
                  </div>
 | 
					 | 
				
			||||||
                </PopoverPanel>
 | 
					 | 
				
			||||||
              </Transition>
 | 
					 | 
				
			||||||
            </Popover>
 | 
					 | 
				
			||||||
            <DeleteChat
 | 
					 | 
				
			||||||
              redirect
 | 
					 | 
				
			||||||
              chatId={chatId!}
 | 
					 | 
				
			||||||
              chats={[]}
 | 
					 | 
				
			||||||
              setChats={() => {}}
 | 
					 | 
				
			||||||
            />
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,71 +0,0 @@
 | 
				
			|||||||
import { useEffect, useState } from 'react';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface Article {
 | 
					 | 
				
			||||||
  title: string;
 | 
					 | 
				
			||||||
  content: string;
 | 
					 | 
				
			||||||
  url: string;
 | 
					 | 
				
			||||||
  thumbnail: string;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const NewsArticleWidget = () => {
 | 
					 | 
				
			||||||
  const [article, setArticle] = useState<Article | null>(null);
 | 
					 | 
				
			||||||
  const [loading, setLoading] = useState(true);
 | 
					 | 
				
			||||||
  const [error, setError] = useState(false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  useEffect(() => {
 | 
					 | 
				
			||||||
    fetch('/api/discover?mode=preview')
 | 
					 | 
				
			||||||
      .then((res) => res.json())
 | 
					 | 
				
			||||||
      .then((data) => {
 | 
					 | 
				
			||||||
        const articles = (data.blogs || []).filter((a: Article) => a.thumbnail);
 | 
					 | 
				
			||||||
        setArticle(articles[Math.floor(Math.random() * articles.length)]);
 | 
					 | 
				
			||||||
        setLoading(false);
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
      .catch(() => {
 | 
					 | 
				
			||||||
        setError(true);
 | 
					 | 
				
			||||||
        setLoading(false);
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
  }, []);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return (
 | 
					 | 
				
			||||||
    <div className="bg-light-secondary dark:bg-dark-secondary rounded-2xl border border-light-200 dark:border-dark-200 shadow-sm shadow-light-200/10 dark:shadow-black/25 flex flex-row items-stretch w-full h-24 min-h-[96px] max-h-[96px] p-0 overflow-hidden">
 | 
					 | 
				
			||||||
      {loading ? (
 | 
					 | 
				
			||||||
        <div className="animate-pulse flex flex-row items-stretch w-full h-full">
 | 
					 | 
				
			||||||
          <div className="w-24 min-w-24 max-w-24 h-full bg-light-200 dark:bg-dark-200" />
 | 
					 | 
				
			||||||
          <div className="flex flex-col justify-center flex-1 px-3 py-2 gap-2">
 | 
					 | 
				
			||||||
            <div className="h-4 w-3/4 rounded bg-light-200 dark:bg-dark-200" />
 | 
					 | 
				
			||||||
            <div className="h-3 w-1/2 rounded bg-light-200 dark:bg-dark-200" />
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
      ) : error ? (
 | 
					 | 
				
			||||||
        <div className="w-full text-xs text-red-400">Could not load news.</div>
 | 
					 | 
				
			||||||
      ) : article ? (
 | 
					 | 
				
			||||||
        <a
 | 
					 | 
				
			||||||
          href={`/?q=Summary: ${article.url}`}
 | 
					 | 
				
			||||||
          className="flex flex-row items-stretch w-full h-full relative overflow-hidden group"
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
          <div className="relative w-24 min-w-24 max-w-24 h-full overflow-hidden">
 | 
					 | 
				
			||||||
            <img
 | 
					 | 
				
			||||||
              className="object-cover w-full h-full bg-light-200 dark:bg-dark-200 group-hover:scale-110 transition-transform duration-300"
 | 
					 | 
				
			||||||
              src={
 | 
					 | 
				
			||||||
                new URL(article.thumbnail).origin +
 | 
					 | 
				
			||||||
                new URL(article.thumbnail).pathname +
 | 
					 | 
				
			||||||
                `?id=${new URL(article.thumbnail).searchParams.get('id')}`
 | 
					 | 
				
			||||||
              }
 | 
					 | 
				
			||||||
              alt={article.title}
 | 
					 | 
				
			||||||
            />
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div className="flex flex-col justify-center flex-1 px-3 py-2">
 | 
					 | 
				
			||||||
            <div className="font-semibold text-xs text-black dark:text-white leading-tight line-clamp-2 mb-1">
 | 
					 | 
				
			||||||
              {article.title}
 | 
					 | 
				
			||||||
            </div>
 | 
					 | 
				
			||||||
            <p className="text-black/60 dark:text-white/60 text-[10px] leading-relaxed line-clamp-2">
 | 
					 | 
				
			||||||
              {article.content}
 | 
					 | 
				
			||||||
            </p>
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
        </a>
 | 
					 | 
				
			||||||
      ) : null}
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default NewsArticleWidget;
 | 
					 | 
				
			||||||
@@ -33,10 +33,11 @@ const SearchImages = ({
 | 
				
			|||||||
          onClick={async () => {
 | 
					          onClick={async () => {
 | 
				
			||||||
            setLoading(true);
 | 
					            setLoading(true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            const chatModelProvider = localStorage.getItem(
 | 
					            const chatModelProvider = localStorage.getItem('chatModelProvider');
 | 
				
			||||||
              'chatModelProviderId',
 | 
					            const chatModel = localStorage.getItem('chatModel');
 | 
				
			||||||
            );
 | 
					
 | 
				
			||||||
            const chatModel = localStorage.getItem('chatModelKey');
 | 
					            const customOpenAIBaseURL = localStorage.getItem('openAIBaseURL');
 | 
				
			||||||
 | 
					            const customOpenAIKey = localStorage.getItem('openAIApiKey');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            const res = await fetch(`/api/images`, {
 | 
					            const res = await fetch(`/api/images`, {
 | 
				
			||||||
              method: 'POST',
 | 
					              method: 'POST',
 | 
				
			||||||
@@ -47,8 +48,12 @@ const SearchImages = ({
 | 
				
			|||||||
                query: query,
 | 
					                query: query,
 | 
				
			||||||
                chatHistory: chatHistory,
 | 
					                chatHistory: chatHistory,
 | 
				
			||||||
                chatModel: {
 | 
					                chatModel: {
 | 
				
			||||||
                  providerId: chatModelProvider,
 | 
					                  provider: chatModelProvider,
 | 
				
			||||||
                  key: chatModel,
 | 
					                  model: chatModel,
 | 
				
			||||||
 | 
					                  ...(chatModelProvider === 'custom_openai' && {
 | 
				
			||||||
 | 
					                    customOpenAIBaseURL: customOpenAIBaseURL,
 | 
				
			||||||
 | 
					                    customOpenAIKey: customOpenAIKey,
 | 
				
			||||||
 | 
					                  }),
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
              }),
 | 
					              }),
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -48,10 +48,11 @@ const Searchvideos = ({
 | 
				
			|||||||
          onClick={async () => {
 | 
					          onClick={async () => {
 | 
				
			||||||
            setLoading(true);
 | 
					            setLoading(true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            const chatModelProvider = localStorage.getItem(
 | 
					            const chatModelProvider = localStorage.getItem('chatModelProvider');
 | 
				
			||||||
              'chatModelProviderId',
 | 
					            const chatModel = localStorage.getItem('chatModel');
 | 
				
			||||||
            );
 | 
					
 | 
				
			||||||
            const chatModel = localStorage.getItem('chatModelKey');
 | 
					            const customOpenAIBaseURL = localStorage.getItem('openAIBaseURL');
 | 
				
			||||||
 | 
					            const customOpenAIKey = localStorage.getItem('openAIApiKey');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            const res = await fetch(`/api/videos`, {
 | 
					            const res = await fetch(`/api/videos`, {
 | 
				
			||||||
              method: 'POST',
 | 
					              method: 'POST',
 | 
				
			||||||
@@ -62,8 +63,12 @@ const Searchvideos = ({
 | 
				
			|||||||
                query: query,
 | 
					                query: query,
 | 
				
			||||||
                chatHistory: chatHistory,
 | 
					                chatHistory: chatHistory,
 | 
				
			||||||
                chatModel: {
 | 
					                chatModel: {
 | 
				
			||||||
                  providerId: chatModelProvider,
 | 
					                  provider: chatModelProvider,
 | 
				
			||||||
                  key: chatModel,
 | 
					                  model: chatModel,
 | 
				
			||||||
 | 
					                  ...(chatModelProvider === 'custom_openai' && {
 | 
				
			||||||
 | 
					                    customOpenAIBaseURL: customOpenAIBaseURL,
 | 
				
			||||||
 | 
					                    customOpenAIKey: customOpenAIKey,
 | 
				
			||||||
 | 
					                  }),
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
              }),
 | 
					              }),
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,29 +0,0 @@
 | 
				
			|||||||
import { UIConfigField } from '@/lib/config/types';
 | 
					 | 
				
			||||||
import SettingsField from '../SettingsField';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const General = ({
 | 
					 | 
				
			||||||
  fields,
 | 
					 | 
				
			||||||
  values,
 | 
					 | 
				
			||||||
}: {
 | 
					 | 
				
			||||||
  fields: UIConfigField[];
 | 
					 | 
				
			||||||
  values: Record<string, any>;
 | 
					 | 
				
			||||||
}) => {
 | 
					 | 
				
			||||||
  return (
 | 
					 | 
				
			||||||
    <div className="flex-1 space-y-6 overflow-y-auto px-6 py-6">
 | 
					 | 
				
			||||||
      {fields.map((field) => (
 | 
					 | 
				
			||||||
        <SettingsField
 | 
					 | 
				
			||||||
          key={field.key}
 | 
					 | 
				
			||||||
          field={field}
 | 
					 | 
				
			||||||
          value={
 | 
					 | 
				
			||||||
            (field.scope === 'client'
 | 
					 | 
				
			||||||
              ? localStorage.getItem(field.key)
 | 
					 | 
				
			||||||
              : values[field.key]) ?? field.default
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
          dataAdd="general"
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
      ))}
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default General;
 | 
					 | 
				
			||||||
@@ -1,163 +0,0 @@
 | 
				
			|||||||
import { Dialog, DialogPanel } from '@headlessui/react';
 | 
					 | 
				
			||||||
import { Loader2, Plus } from 'lucide-react';
 | 
					 | 
				
			||||||
import { useState } from 'react';
 | 
					 | 
				
			||||||
import { AnimatePresence, motion } from 'framer-motion';
 | 
					 | 
				
			||||||
import { ConfigModelProvider } from '@/lib/config/types';
 | 
					 | 
				
			||||||
import { toast } from 'sonner';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const AddModel = ({
 | 
					 | 
				
			||||||
  providerId,
 | 
					 | 
				
			||||||
  setProviders,
 | 
					 | 
				
			||||||
  type,
 | 
					 | 
				
			||||||
}: {
 | 
					 | 
				
			||||||
  providerId: string;
 | 
					 | 
				
			||||||
  setProviders: React.Dispatch<React.SetStateAction<ConfigModelProvider[]>>;
 | 
					 | 
				
			||||||
  type: 'chat' | 'embedding';
 | 
					 | 
				
			||||||
}) => {
 | 
					 | 
				
			||||||
  const [open, setOpen] = useState(false);
 | 
					 | 
				
			||||||
  const [modelName, setModelName] = useState('');
 | 
					 | 
				
			||||||
  const [modelKey, setModelKey] = useState('');
 | 
					 | 
				
			||||||
  const [loading, setLoading] = useState(false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const handleSubmit = async (e: React.FormEvent) => {
 | 
					 | 
				
			||||||
    e.preventDefault();
 | 
					 | 
				
			||||||
    setLoading(true);
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      const res = await fetch(`/api/providers/${providerId}/models`, {
 | 
					 | 
				
			||||||
        method: 'POST',
 | 
					 | 
				
			||||||
        headers: {
 | 
					 | 
				
			||||||
          'Content-Type': 'application/json',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        body: JSON.stringify({
 | 
					 | 
				
			||||||
          name: modelName,
 | 
					 | 
				
			||||||
          key: modelKey,
 | 
					 | 
				
			||||||
          type: type,
 | 
					 | 
				
			||||||
        }),
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if (!res.ok) {
 | 
					 | 
				
			||||||
        throw new Error('Failed to add model');
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      setProviders((prev) =>
 | 
					 | 
				
			||||||
        prev.map((provider) => {
 | 
					 | 
				
			||||||
          if (provider.id === providerId) {
 | 
					 | 
				
			||||||
            const newModel = { name: modelName, key: modelKey };
 | 
					 | 
				
			||||||
            return {
 | 
					 | 
				
			||||||
              ...provider,
 | 
					 | 
				
			||||||
              chatModels:
 | 
					 | 
				
			||||||
                type === 'chat'
 | 
					 | 
				
			||||||
                  ? [...provider.chatModels, newModel]
 | 
					 | 
				
			||||||
                  : provider.chatModels,
 | 
					 | 
				
			||||||
              embeddingModels:
 | 
					 | 
				
			||||||
                type === 'embedding'
 | 
					 | 
				
			||||||
                  ? [...provider.embeddingModels, newModel]
 | 
					 | 
				
			||||||
                  : provider.embeddingModels,
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
          return provider;
 | 
					 | 
				
			||||||
        }),
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      toast.success('Model added successfully.');
 | 
					 | 
				
			||||||
      setModelName('');
 | 
					 | 
				
			||||||
      setModelKey('');
 | 
					 | 
				
			||||||
      setOpen(false);
 | 
					 | 
				
			||||||
    } catch (error) {
 | 
					 | 
				
			||||||
      console.error('Error adding model:', error);
 | 
					 | 
				
			||||||
      toast.error('Failed to add model.');
 | 
					 | 
				
			||||||
    } finally {
 | 
					 | 
				
			||||||
      setLoading(false);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return (
 | 
					 | 
				
			||||||
    <>
 | 
					 | 
				
			||||||
      <button
 | 
					 | 
				
			||||||
        onClick={() => setOpen(true)}
 | 
					 | 
				
			||||||
        className="text-xs text-black/70 dark:text-white/70 hover:text-black hover:dark:text-white flex flex-row items-center space-x-1 active:scale-95 transition duration-200"
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        <Plus size={12} />
 | 
					 | 
				
			||||||
        <span>Add</span>
 | 
					 | 
				
			||||||
      </button>
 | 
					 | 
				
			||||||
      <AnimatePresence>
 | 
					 | 
				
			||||||
        {open && (
 | 
					 | 
				
			||||||
          <Dialog
 | 
					 | 
				
			||||||
            static
 | 
					 | 
				
			||||||
            open={open}
 | 
					 | 
				
			||||||
            onClose={() => setOpen(false)}
 | 
					 | 
				
			||||||
            className="relative z-[60]"
 | 
					 | 
				
			||||||
          >
 | 
					 | 
				
			||||||
            <motion.div
 | 
					 | 
				
			||||||
              initial={{ opacity: 0 }}
 | 
					 | 
				
			||||||
              animate={{ opacity: 1 }}
 | 
					 | 
				
			||||||
              exit={{ opacity: 0 }}
 | 
					 | 
				
			||||||
              transition={{ duration: 0.1 }}
 | 
					 | 
				
			||||||
              className="fixed inset-0 flex w-screen items-center justify-center p-4 bg-black/30 backdrop-blur-sm"
 | 
					 | 
				
			||||||
            >
 | 
					 | 
				
			||||||
              <DialogPanel className="w-full mx-4 lg:w-[600px] max-h-[85vh] flex flex-col border bg-light-primary dark:bg-dark-primary border-light-secondary dark:border-dark-secondary rounded-lg">
 | 
					 | 
				
			||||||
                <div className="px-6 pt-6 pb-4">
 | 
					 | 
				
			||||||
                  <h3 className="text-black/90 dark:text-white/90 font-medium">
 | 
					 | 
				
			||||||
                    Add new {type === 'chat' ? 'chat' : 'embedding'} model
 | 
					 | 
				
			||||||
                  </h3>
 | 
					 | 
				
			||||||
                </div>
 | 
					 | 
				
			||||||
                <div className="border-t border-light-200 dark:border-dark-200" />
 | 
					 | 
				
			||||||
                <div className="flex-1 overflow-y-auto px-6 py-4">
 | 
					 | 
				
			||||||
                  <form
 | 
					 | 
				
			||||||
                    onSubmit={handleSubmit}
 | 
					 | 
				
			||||||
                    className="flex flex-col h-full"
 | 
					 | 
				
			||||||
                  >
 | 
					 | 
				
			||||||
                    <div className="flex flex-col space-y-4 flex-1">
 | 
					 | 
				
			||||||
                      <div className="flex flex-col items-start space-y-2">
 | 
					 | 
				
			||||||
                        <label className="text-xs text-black/70 dark:text-white/70">
 | 
					 | 
				
			||||||
                          Model name*
 | 
					 | 
				
			||||||
                        </label>
 | 
					 | 
				
			||||||
                        <input
 | 
					 | 
				
			||||||
                          value={modelName}
 | 
					 | 
				
			||||||
                          onChange={(e) => setModelName(e.target.value)}
 | 
					 | 
				
			||||||
                          className="w-full rounded-lg border border-light-200 dark:border-dark-200 bg-light-primary dark:bg-dark-primary px-4 py-3 text-sm text-black/80 dark:text-white/80 placeholder:text-black/40 dark:placeholder:text-white/40 focus-visible:outline-none focus-visible:border-light-300 dark:focus-visible:border-dark-300 transition-colors disabled:cursor-not-allowed disabled:opacity-60"
 | 
					 | 
				
			||||||
                          placeholder="e.g., GPT-4"
 | 
					 | 
				
			||||||
                          type="text"
 | 
					 | 
				
			||||||
                          required
 | 
					 | 
				
			||||||
                        />
 | 
					 | 
				
			||||||
                      </div>
 | 
					 | 
				
			||||||
                      <div className="flex flex-col items-start space-y-2">
 | 
					 | 
				
			||||||
                        <label className="text-xs text-black/70 dark:text-white/70">
 | 
					 | 
				
			||||||
                          Model key*
 | 
					 | 
				
			||||||
                        </label>
 | 
					 | 
				
			||||||
                        <input
 | 
					 | 
				
			||||||
                          value={modelKey}
 | 
					 | 
				
			||||||
                          onChange={(e) => setModelKey(e.target.value)}
 | 
					 | 
				
			||||||
                          className="w-full rounded-lg border border-light-200 dark:border-dark-200 bg-light-primary dark:bg-dark-primary px-4 py-3 text-sm text-black/80 dark:text-white/80 placeholder:text-black/40 dark:placeholder:text-white/40 focus-visible:outline-none focus-visible:border-light-300 dark:focus-visible:border-dark-300 transition-colors disabled:cursor-not-allowed disabled:opacity-60"
 | 
					 | 
				
			||||||
                          placeholder="e.g., gpt-4"
 | 
					 | 
				
			||||||
                          type="text"
 | 
					 | 
				
			||||||
                          required
 | 
					 | 
				
			||||||
                        />
 | 
					 | 
				
			||||||
                      </div>
 | 
					 | 
				
			||||||
                    </div>
 | 
					 | 
				
			||||||
                    <div className="border-t border-light-200 dark:border-dark-200 -mx-6 my-4" />
 | 
					 | 
				
			||||||
                    <div className="flex justify-end">
 | 
					 | 
				
			||||||
                      <button
 | 
					 | 
				
			||||||
                        type="submit"
 | 
					 | 
				
			||||||
                        disabled={loading}
 | 
					 | 
				
			||||||
                        className="px-4 py-2 rounded-lg text-sm bg-sky-500 text-white font-medium disabled:opacity-85 hover:opacity-85 active:scale-95 transition duration-200"
 | 
					 | 
				
			||||||
                      >
 | 
					 | 
				
			||||||
                        {loading ? (
 | 
					 | 
				
			||||||
                          <Loader2 className="animate-spin" size={16} />
 | 
					 | 
				
			||||||
                        ) : (
 | 
					 | 
				
			||||||
                          'Add Model'
 | 
					 | 
				
			||||||
                        )}
 | 
					 | 
				
			||||||
                      </button>
 | 
					 | 
				
			||||||
                    </div>
 | 
					 | 
				
			||||||
                  </form>
 | 
					 | 
				
			||||||
                </div>
 | 
					 | 
				
			||||||
              </DialogPanel>
 | 
					 | 
				
			||||||
            </motion.div>
 | 
					 | 
				
			||||||
          </Dialog>
 | 
					 | 
				
			||||||
        )}
 | 
					 | 
				
			||||||
      </AnimatePresence>
 | 
					 | 
				
			||||||
    </>
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default AddModel;
 | 
					 | 
				
			||||||
@@ -1,216 +0,0 @@
 | 
				
			|||||||
import {
 | 
					 | 
				
			||||||
  Description,
 | 
					 | 
				
			||||||
  Dialog,
 | 
					 | 
				
			||||||
  DialogPanel,
 | 
					 | 
				
			||||||
  DialogTitle,
 | 
					 | 
				
			||||||
} from '@headlessui/react';
 | 
					 | 
				
			||||||
import { Loader2, Plus } from 'lucide-react';
 | 
					 | 
				
			||||||
import { useMemo, useState } from 'react';
 | 
					 | 
				
			||||||
import { AnimatePresence, motion } from 'framer-motion';
 | 
					 | 
				
			||||||
import {
 | 
					 | 
				
			||||||
  ConfigModelProvider,
 | 
					 | 
				
			||||||
  ModelProviderUISection,
 | 
					 | 
				
			||||||
  StringUIConfigField,
 | 
					 | 
				
			||||||
  UIConfigField,
 | 
					 | 
				
			||||||
} from '@/lib/config/types';
 | 
					 | 
				
			||||||
import Select from '@/components/ui/Select';
 | 
					 | 
				
			||||||
import { toast } from 'sonner';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const AddProvider = ({
 | 
					 | 
				
			||||||
  modelProviders,
 | 
					 | 
				
			||||||
  setProviders,
 | 
					 | 
				
			||||||
}: {
 | 
					 | 
				
			||||||
  modelProviders: ModelProviderUISection[];
 | 
					 | 
				
			||||||
  setProviders: React.Dispatch<React.SetStateAction<ConfigModelProvider[]>>;
 | 
					 | 
				
			||||||
}) => {
 | 
					 | 
				
			||||||
  const [open, setOpen] = useState(false);
 | 
					 | 
				
			||||||
  const [selectedProvider, setSelectedProvider] = useState<null | string>(
 | 
					 | 
				
			||||||
    modelProviders[0]?.key || null,
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
  const [config, setConfig] = useState<Record<string, any>>({});
 | 
					 | 
				
			||||||
  const [name, setName] = useState('');
 | 
					 | 
				
			||||||
  const [loading, setLoading] = useState(false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const providerConfigMap = useMemo(() => {
 | 
					 | 
				
			||||||
    const map: Record<string, { name: string; fields: UIConfigField[] }> = {};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    modelProviders.forEach((p) => {
 | 
					 | 
				
			||||||
      map[p.key] = {
 | 
					 | 
				
			||||||
        name: p.name,
 | 
					 | 
				
			||||||
        fields: p.fields,
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return map;
 | 
					 | 
				
			||||||
  }, [modelProviders]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const selectedProviderFields = useMemo(() => {
 | 
					 | 
				
			||||||
    if (!selectedProvider) return [];
 | 
					 | 
				
			||||||
    const providerFields = providerConfigMap[selectedProvider]?.fields || [];
 | 
					 | 
				
			||||||
    const config: Record<string, any> = {};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    providerFields.forEach((field) => {
 | 
					 | 
				
			||||||
      config[field.key] = field.default || '';
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    setConfig(config);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return providerFields;
 | 
					 | 
				
			||||||
  }, [selectedProvider, providerConfigMap]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const handleSubmit = async (e: React.FormEvent) => {
 | 
					 | 
				
			||||||
    e.preventDefault();
 | 
					 | 
				
			||||||
    setLoading(true);
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      const res = await fetch('/api/providers', {
 | 
					 | 
				
			||||||
        method: 'POST',
 | 
					 | 
				
			||||||
        headers: {
 | 
					 | 
				
			||||||
          'Content-Type': 'application/json',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        body: JSON.stringify({
 | 
					 | 
				
			||||||
          type: selectedProvider,
 | 
					 | 
				
			||||||
          name: name,
 | 
					 | 
				
			||||||
          config: config,
 | 
					 | 
				
			||||||
        }),
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if (!res.ok) {
 | 
					 | 
				
			||||||
        throw new Error('Failed to add provider');
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const data: ConfigModelProvider = (await res.json()).provider;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      setProviders((prev) => [...prev, data]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      toast.success('Provider added successfully.');
 | 
					 | 
				
			||||||
    } catch (error) {
 | 
					 | 
				
			||||||
      console.error('Error adding provider:', error);
 | 
					 | 
				
			||||||
      toast.error('Failed to add provider.');
 | 
					 | 
				
			||||||
    } finally {
 | 
					 | 
				
			||||||
      setLoading(false);
 | 
					 | 
				
			||||||
      setOpen(false);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return (
 | 
					 | 
				
			||||||
    <>
 | 
					 | 
				
			||||||
      <button
 | 
					 | 
				
			||||||
        onClick={() => setOpen(true)}
 | 
					 | 
				
			||||||
        className="px-3 md:px-4 py-1.5 md:py-2 rounded-lg text-xs sm:text-sm border border-light-200 dark:border-dark-200 text-black dark:text-white bg-light-secondary/50 dark:bg-dark-secondary/50 hover:bg-light-secondary hover:dark:bg-dark-secondary hover:border-light-300 hover:dark:border-dark-300 flex flex-row items-center space-x-1 active:scale-95 transition duration-200"
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        <Plus className="w-3.5 h-3.5 md:w-4 md:h-4" />
 | 
					 | 
				
			||||||
        <span>Add Provider</span>
 | 
					 | 
				
			||||||
      </button>
 | 
					 | 
				
			||||||
      <AnimatePresence>
 | 
					 | 
				
			||||||
        {open && (
 | 
					 | 
				
			||||||
          <Dialog
 | 
					 | 
				
			||||||
            static
 | 
					 | 
				
			||||||
            open={open}
 | 
					 | 
				
			||||||
            onClose={() => setOpen(false)}
 | 
					 | 
				
			||||||
            className="relative z-[60]"
 | 
					 | 
				
			||||||
          >
 | 
					 | 
				
			||||||
            <motion.div
 | 
					 | 
				
			||||||
              initial={{ opacity: 0 }}
 | 
					 | 
				
			||||||
              animate={{ opacity: 1 }}
 | 
					 | 
				
			||||||
              exit={{ opacity: 0 }}
 | 
					 | 
				
			||||||
              transition={{ duration: 0.1 }}
 | 
					 | 
				
			||||||
              className="fixed inset-0 flex w-screen items-center justify-center p-4 bg-black/30 backdrop-blur-sm"
 | 
					 | 
				
			||||||
            >
 | 
					 | 
				
			||||||
              <DialogPanel className="w-full mx-4 lg:w-[600px] max-h-[85vh] flex flex-col border bg-light-primary dark:bg-dark-primary border-light-secondary dark:border-dark-secondary rounded-lg">
 | 
					 | 
				
			||||||
                <form onSubmit={handleSubmit} className="flex flex-col flex-1">
 | 
					 | 
				
			||||||
                  <div className="px-6 pt-6 pb-4">
 | 
					 | 
				
			||||||
                    <h3 className="text-black/90 dark:text-white/90 font-medium">
 | 
					 | 
				
			||||||
                      Add new provider
 | 
					 | 
				
			||||||
                    </h3>
 | 
					 | 
				
			||||||
                  </div>
 | 
					 | 
				
			||||||
                  <div className="border-t border-light-200 dark:border-dark-200" />
 | 
					 | 
				
			||||||
                  <div className="flex-1 overflow-y-auto px-6 py-4">
 | 
					 | 
				
			||||||
                    <div className="flex flex-col space-y-4">
 | 
					 | 
				
			||||||
                      <div className="flex flex-col items-start space-y-2">
 | 
					 | 
				
			||||||
                        <label className="text-xs text-black/70 dark:text-white/70">
 | 
					 | 
				
			||||||
                          Select provider type
 | 
					 | 
				
			||||||
                        </label>
 | 
					 | 
				
			||||||
                        <Select
 | 
					 | 
				
			||||||
                          value={selectedProvider ?? ''}
 | 
					 | 
				
			||||||
                          onChange={(e) => setSelectedProvider(e.target.value)}
 | 
					 | 
				
			||||||
                          options={Object.entries(providerConfigMap).map(
 | 
					 | 
				
			||||||
                            ([key, val]) => {
 | 
					 | 
				
			||||||
                              return {
 | 
					 | 
				
			||||||
                                label: val.name,
 | 
					 | 
				
			||||||
                                value: key,
 | 
					 | 
				
			||||||
                              };
 | 
					 | 
				
			||||||
                            },
 | 
					 | 
				
			||||||
                          )}
 | 
					 | 
				
			||||||
                        />
 | 
					 | 
				
			||||||
                      </div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                      <div
 | 
					 | 
				
			||||||
                        key="name"
 | 
					 | 
				
			||||||
                        className="flex flex-col items-start space-y-2"
 | 
					 | 
				
			||||||
                      >
 | 
					 | 
				
			||||||
                        <label className="text-xs text-black/70 dark:text-white/70">
 | 
					 | 
				
			||||||
                          Name*
 | 
					 | 
				
			||||||
                        </label>
 | 
					 | 
				
			||||||
                        <input
 | 
					 | 
				
			||||||
                          value={name}
 | 
					 | 
				
			||||||
                          onChange={(e) => setName(e.target.value)}
 | 
					 | 
				
			||||||
                          className="w-full rounded-lg border border-light-200 dark:border-dark-200 bg-light-primary dark:bg-dark-primary px-4 py-3 pr-10 text-sm text-black/80 dark:text-white/80 placeholder:text-black/40 dark:placeholder:text-white/40 focus-visible:outline-none focus-visible:border-light-300 dark:focus-visible:border-dark-300 transition-colors disabled:cursor-not-allowed disabled:opacity-60"
 | 
					 | 
				
			||||||
                          placeholder={'Provider Name'}
 | 
					 | 
				
			||||||
                          type="text"
 | 
					 | 
				
			||||||
                          required={true}
 | 
					 | 
				
			||||||
                        />
 | 
					 | 
				
			||||||
                      </div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                      {selectedProviderFields.map((field: UIConfigField) => (
 | 
					 | 
				
			||||||
                        <div
 | 
					 | 
				
			||||||
                          key={field.key}
 | 
					 | 
				
			||||||
                          className="flex flex-col items-start space-y-2"
 | 
					 | 
				
			||||||
                        >
 | 
					 | 
				
			||||||
                          <label className="text-xs text-black/70 dark:text-white/70">
 | 
					 | 
				
			||||||
                            {field.name}
 | 
					 | 
				
			||||||
                            {field.required && '*'}
 | 
					 | 
				
			||||||
                          </label>
 | 
					 | 
				
			||||||
                          <input
 | 
					 | 
				
			||||||
                            value={config[field.key] ?? field.default ?? ''}
 | 
					 | 
				
			||||||
                            onChange={(event) =>
 | 
					 | 
				
			||||||
                              setConfig((prev) => ({
 | 
					 | 
				
			||||||
                                ...prev,
 | 
					 | 
				
			||||||
                                [field.key]: event.target.value,
 | 
					 | 
				
			||||||
                              }))
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
                            className="w-full rounded-lg border border-light-200 dark:border-dark-200 bg-light-primary dark:bg-dark-primary px-4 py-3 pr-10 text-sm text-black/80 dark:text-white/80 placeholder:text-black/40 dark:placeholder:text-white/40 focus-visible:outline-none focus-visible:border-light-300 dark:focus-visible:border-dark-300 transition-colors disabled:cursor-not-allowed disabled:opacity-60"
 | 
					 | 
				
			||||||
                            placeholder={
 | 
					 | 
				
			||||||
                              (field as StringUIConfigField).placeholder
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
                            type="text"
 | 
					 | 
				
			||||||
                            required={field.required}
 | 
					 | 
				
			||||||
                          />
 | 
					 | 
				
			||||||
                        </div>
 | 
					 | 
				
			||||||
                      ))}
 | 
					 | 
				
			||||||
                    </div>
 | 
					 | 
				
			||||||
                  </div>
 | 
					 | 
				
			||||||
                  <div className="border-t border-light-200 dark:border-dark-200" />
 | 
					 | 
				
			||||||
                  <div className="px-6 py-4 flex justify-end">
 | 
					 | 
				
			||||||
                    <button
 | 
					 | 
				
			||||||
                      type="submit"
 | 
					 | 
				
			||||||
                      disabled={loading}
 | 
					 | 
				
			||||||
                      className="px-4 py-2 rounded-lg text-sm bg-sky-500 text-white font-medium disabled:opacity-85 hover:opacity-85 active:scale-95 transition duration-200"
 | 
					 | 
				
			||||||
                    >
 | 
					 | 
				
			||||||
                      {loading ? (
 | 
					 | 
				
			||||||
                        <Loader2 className="animate-spin" size={16} />
 | 
					 | 
				
			||||||
                      ) : (
 | 
					 | 
				
			||||||
                        'Add Provider'
 | 
					 | 
				
			||||||
                      )}
 | 
					 | 
				
			||||||
                    </button>
 | 
					 | 
				
			||||||
                  </div>
 | 
					 | 
				
			||||||
                </form>
 | 
					 | 
				
			||||||
              </DialogPanel>
 | 
					 | 
				
			||||||
            </motion.div>
 | 
					 | 
				
			||||||
          </Dialog>
 | 
					 | 
				
			||||||
        )}
 | 
					 | 
				
			||||||
      </AnimatePresence>
 | 
					 | 
				
			||||||
    </>
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default AddProvider;
 | 
					 | 
				
			||||||
@@ -1,118 +0,0 @@
 | 
				
			|||||||
import { Dialog, DialogPanel } from '@headlessui/react';
 | 
					 | 
				
			||||||
import { Loader2, Trash2 } from 'lucide-react';
 | 
					 | 
				
			||||||
import { useState } from 'react';
 | 
					 | 
				
			||||||
import { AnimatePresence, motion } from 'framer-motion';
 | 
					 | 
				
			||||||
import { ConfigModelProvider } from '@/lib/config/types';
 | 
					 | 
				
			||||||
import { toast } from 'sonner';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const DeleteProvider = ({
 | 
					 | 
				
			||||||
  modelProvider,
 | 
					 | 
				
			||||||
  setProviders,
 | 
					 | 
				
			||||||
}: {
 | 
					 | 
				
			||||||
  modelProvider: ConfigModelProvider;
 | 
					 | 
				
			||||||
  setProviders: React.Dispatch<React.SetStateAction<ConfigModelProvider[]>>;
 | 
					 | 
				
			||||||
}) => {
 | 
					 | 
				
			||||||
  const [open, setOpen] = useState(false);
 | 
					 | 
				
			||||||
  const [loading, setLoading] = useState(false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const handleDelete = async (e: React.FormEvent) => {
 | 
					 | 
				
			||||||
    e.preventDefault();
 | 
					 | 
				
			||||||
    setLoading(true);
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      const res = await fetch(`/api/providers/${modelProvider.id}`, {
 | 
					 | 
				
			||||||
        method: 'DELETE',
 | 
					 | 
				
			||||||
        headers: {
 | 
					 | 
				
			||||||
          'Content-Type': 'application/json',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if (!res.ok) {
 | 
					 | 
				
			||||||
        throw new Error('Failed to delete provider');
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      setProviders((prev) => {
 | 
					 | 
				
			||||||
        return prev.filter((p) => p.id !== modelProvider.id);
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      toast.success('Provider deleted successfully.');
 | 
					 | 
				
			||||||
    } catch (error) {
 | 
					 | 
				
			||||||
      console.error('Error deleting provider:', error);
 | 
					 | 
				
			||||||
      toast.error('Failed to delete provider.');
 | 
					 | 
				
			||||||
    } finally {
 | 
					 | 
				
			||||||
      setLoading(false);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return (
 | 
					 | 
				
			||||||
    <>
 | 
					 | 
				
			||||||
      <button
 | 
					 | 
				
			||||||
        onClick={(e) => {
 | 
					 | 
				
			||||||
          e.stopPropagation();
 | 
					 | 
				
			||||||
          setOpen(true);
 | 
					 | 
				
			||||||
        }}
 | 
					 | 
				
			||||||
        className="group p-1.5 rounded-md hover:bg-light-200 hover:dark:bg-dark-200 transition-colors group"
 | 
					 | 
				
			||||||
        title="Delete provider"
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        <Trash2
 | 
					 | 
				
			||||||
          size={14}
 | 
					 | 
				
			||||||
          className="text-black/60 dark:text-white/60 group-hover:text-red-500 group-hover:dark:text-red-400"
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
      </button>
 | 
					 | 
				
			||||||
      <AnimatePresence>
 | 
					 | 
				
			||||||
        {open && (
 | 
					 | 
				
			||||||
          <Dialog
 | 
					 | 
				
			||||||
            static
 | 
					 | 
				
			||||||
            open={open}
 | 
					 | 
				
			||||||
            onClose={() => setOpen(false)}
 | 
					 | 
				
			||||||
            className="relative z-[60]"
 | 
					 | 
				
			||||||
          >
 | 
					 | 
				
			||||||
            <motion.div
 | 
					 | 
				
			||||||
              initial={{ opacity: 0 }}
 | 
					 | 
				
			||||||
              animate={{ opacity: 1 }}
 | 
					 | 
				
			||||||
              exit={{ opacity: 0 }}
 | 
					 | 
				
			||||||
              transition={{ duration: 0.1 }}
 | 
					 | 
				
			||||||
              className="fixed inset-0 flex w-screen items-center justify-center p-4 bg-black/30 backdrop-blur-sm"
 | 
					 | 
				
			||||||
            >
 | 
					 | 
				
			||||||
              <DialogPanel className="w-full mx-4 lg:w-[600px] max-h-[85vh] flex flex-col border bg-light-primary dark:bg-dark-primary border-light-secondary dark:border-dark-secondary rounded-lg">
 | 
					 | 
				
			||||||
                <div className="px-6 pt-6 pb-4">
 | 
					 | 
				
			||||||
                  <h3 className="text-black/90 dark:text-white/90 font-medium">
 | 
					 | 
				
			||||||
                    Delete provider
 | 
					 | 
				
			||||||
                  </h3>
 | 
					 | 
				
			||||||
                </div>
 | 
					 | 
				
			||||||
                <div className="border-t border-light-200 dark:border-dark-200" />
 | 
					 | 
				
			||||||
                <div className="flex-1 overflow-y-auto px-6 py-4">
 | 
					 | 
				
			||||||
                  <p className="text-SM text-black/60 dark:text-white/60">
 | 
					 | 
				
			||||||
                    Are you sure you want to delete the provider "
 | 
					 | 
				
			||||||
                    {modelProvider.name}"? This action cannot be undone.
 | 
					 | 
				
			||||||
                  </p>
 | 
					 | 
				
			||||||
                </div>
 | 
					 | 
				
			||||||
                <div className="px-6 py-6 flex justify-end space-x-2">
 | 
					 | 
				
			||||||
                  <button
 | 
					 | 
				
			||||||
                    disabled={loading}
 | 
					 | 
				
			||||||
                    onClick={() => setOpen(false)}
 | 
					 | 
				
			||||||
                    className="px-4 py-2 rounded-lg text-sm border border-light-200 dark:border-dark-200 text-black dark:text-white bg-light-secondary/50 dark:bg-dark-secondary/50 hover:bg-light-secondary hover:dark:bg-dark-secondary hover:border-light-300 hover:dark:border-dark-300 flex flex-row items-center space-x-1 active:scale-95 transition duration-200"
 | 
					 | 
				
			||||||
                  >
 | 
					 | 
				
			||||||
                    Cancel
 | 
					 | 
				
			||||||
                  </button>
 | 
					 | 
				
			||||||
                  <button
 | 
					 | 
				
			||||||
                    disabled={loading}
 | 
					 | 
				
			||||||
                    onClick={handleDelete}
 | 
					 | 
				
			||||||
                    className="px-4 py-2 rounded-lg text-sm bg-red-500 text-white font-medium disabled:opacity-85 hover:opacity-85 active:scale-95 transition duration-200"
 | 
					 | 
				
			||||||
                  >
 | 
					 | 
				
			||||||
                    {loading ? (
 | 
					 | 
				
			||||||
                      <Loader2 className="animate-spin" size={16} />
 | 
					 | 
				
			||||||
                    ) : (
 | 
					 | 
				
			||||||
                      'Delete'
 | 
					 | 
				
			||||||
                    )}
 | 
					 | 
				
			||||||
                  </button>
 | 
					 | 
				
			||||||
                </div>
 | 
					 | 
				
			||||||
              </DialogPanel>
 | 
					 | 
				
			||||||
            </motion.div>
 | 
					 | 
				
			||||||
          </Dialog>
 | 
					 | 
				
			||||||
        )}
 | 
					 | 
				
			||||||
      </AnimatePresence>
 | 
					 | 
				
			||||||
    </>
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default DeleteProvider;
 | 
					 | 
				
			||||||
@@ -1,213 +0,0 @@
 | 
				
			|||||||
import { UIConfigField, ConfigModelProvider } from '@/lib/config/types';
 | 
					 | 
				
			||||||
import { cn } from '@/lib/utils';
 | 
					 | 
				
			||||||
import { AnimatePresence, motion } from 'framer-motion';
 | 
					 | 
				
			||||||
import { AlertCircle, ChevronDown, Pencil, Trash2, X } from 'lucide-react';
 | 
					 | 
				
			||||||
import { useState } from 'react';
 | 
					 | 
				
			||||||
import { toast } from 'sonner';
 | 
					 | 
				
			||||||
import AddModel from './AddModelDialog';
 | 
					 | 
				
			||||||
import UpdateProvider from './UpdateProviderDialog';
 | 
					 | 
				
			||||||
import DeleteProvider from './DeleteProviderDialog';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const ModelProvider = ({
 | 
					 | 
				
			||||||
  modelProvider,
 | 
					 | 
				
			||||||
  setProviders,
 | 
					 | 
				
			||||||
  fields,
 | 
					 | 
				
			||||||
}: {
 | 
					 | 
				
			||||||
  modelProvider: ConfigModelProvider;
 | 
					 | 
				
			||||||
  fields: UIConfigField[];
 | 
					 | 
				
			||||||
  setProviders: React.Dispatch<React.SetStateAction<ConfigModelProvider[]>>;
 | 
					 | 
				
			||||||
}) => {
 | 
					 | 
				
			||||||
  const [open, setOpen] = useState(false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const handleModelDelete = async (
 | 
					 | 
				
			||||||
    type: 'chat' | 'embedding',
 | 
					 | 
				
			||||||
    modelKey: string,
 | 
					 | 
				
			||||||
  ) => {
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      const res = await fetch(`/api/providers/${modelProvider.id}/models`, {
 | 
					 | 
				
			||||||
        method: 'DELETE',
 | 
					 | 
				
			||||||
        headers: {
 | 
					 | 
				
			||||||
          'Content-Type': 'application/json',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        body: JSON.stringify({ key: modelKey, type: type }),
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if (!res.ok) {
 | 
					 | 
				
			||||||
        throw new Error('Failed to delete model: ' + (await res.text()));
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      setProviders(
 | 
					 | 
				
			||||||
        (prev) =>
 | 
					 | 
				
			||||||
          prev.map((provider) => {
 | 
					 | 
				
			||||||
            if (provider.id === modelProvider.id) {
 | 
					 | 
				
			||||||
              return {
 | 
					 | 
				
			||||||
                ...provider,
 | 
					 | 
				
			||||||
                ...(type === 'chat'
 | 
					 | 
				
			||||||
                  ? {
 | 
					 | 
				
			||||||
                      chatModels: provider.chatModels.filter(
 | 
					 | 
				
			||||||
                        (m) => m.key !== modelKey,
 | 
					 | 
				
			||||||
                      ),
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                  : {
 | 
					 | 
				
			||||||
                      embeddingModels: provider.embeddingModels.filter(
 | 
					 | 
				
			||||||
                        (m) => m.key !== modelKey,
 | 
					 | 
				
			||||||
                      ),
 | 
					 | 
				
			||||||
                    }),
 | 
					 | 
				
			||||||
              };
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            return provider;
 | 
					 | 
				
			||||||
          }) as ConfigModelProvider[],
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      toast.success('Model deleted successfully.');
 | 
					 | 
				
			||||||
    } catch (err) {
 | 
					 | 
				
			||||||
      console.error('Failed to delete model', err);
 | 
					 | 
				
			||||||
      toast.error('Failed to delete model.');
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return (
 | 
					 | 
				
			||||||
    <div
 | 
					 | 
				
			||||||
      key={modelProvider.id}
 | 
					 | 
				
			||||||
      className="border border-light-200 dark:border-dark-200 rounded-lg overflow-hidden"
 | 
					 | 
				
			||||||
    >
 | 
					 | 
				
			||||||
      <div
 | 
					 | 
				
			||||||
        className={cn(
 | 
					 | 
				
			||||||
          'group px-5 py-4 flex flex-row justify-between w-full cursor-pointer hover:bg-light-secondary hover:dark:bg-dark-secondary transition duration-200 items-center',
 | 
					 | 
				
			||||||
          !open && 'rounded-lg',
 | 
					 | 
				
			||||||
        )}
 | 
					 | 
				
			||||||
        onClick={() => setOpen(!open)}
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        <p className="text-sm lg:text-base text-black dark:text-white font-medium">
 | 
					 | 
				
			||||||
          {modelProvider.name}
 | 
					 | 
				
			||||||
        </p>
 | 
					 | 
				
			||||||
        <div className="flex items-center gap-4">
 | 
					 | 
				
			||||||
          <div className="flex flex-row items-center">
 | 
					 | 
				
			||||||
            <UpdateProvider
 | 
					 | 
				
			||||||
              fields={fields}
 | 
					 | 
				
			||||||
              modelProvider={modelProvider}
 | 
					 | 
				
			||||||
              setProviders={setProviders}
 | 
					 | 
				
			||||||
            />
 | 
					 | 
				
			||||||
            <DeleteProvider
 | 
					 | 
				
			||||||
              modelProvider={modelProvider}
 | 
					 | 
				
			||||||
              setProviders={setProviders}
 | 
					 | 
				
			||||||
            />
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <ChevronDown
 | 
					 | 
				
			||||||
            size={16}
 | 
					 | 
				
			||||||
            className={cn(
 | 
					 | 
				
			||||||
              open ? 'rotate-180' : '',
 | 
					 | 
				
			||||||
              'transition duration-200 text-black/70 dark:text-white/70 group-hover:text-sky-500',
 | 
					 | 
				
			||||||
            )}
 | 
					 | 
				
			||||||
          />
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
      <AnimatePresence>
 | 
					 | 
				
			||||||
        {open && (
 | 
					 | 
				
			||||||
          <motion.div
 | 
					 | 
				
			||||||
            initial={{ height: 0, opacity: 0 }}
 | 
					 | 
				
			||||||
            animate={{ height: 'auto', opacity: 1 }}
 | 
					 | 
				
			||||||
            exit={{ height: 0, opacity: 0 }}
 | 
					 | 
				
			||||||
            transition={{ duration: 0.1 }}
 | 
					 | 
				
			||||||
          >
 | 
					 | 
				
			||||||
            <div className="border-t border-light-200 dark:border-dark-200" />
 | 
					 | 
				
			||||||
            <div className="flex flex-col gap-y-4 px-5 py-4">
 | 
					 | 
				
			||||||
              <div className="flex flex-col gap-y-2">
 | 
					 | 
				
			||||||
                <div className="flex flex-row w-full justify-between items-center">
 | 
					 | 
				
			||||||
                  <p className="text-[11px] lg:text-xs text-black/70 dark:text-white/70">
 | 
					 | 
				
			||||||
                    Chat models
 | 
					 | 
				
			||||||
                  </p>
 | 
					 | 
				
			||||||
                  <AddModel
 | 
					 | 
				
			||||||
                    providerId={modelProvider.id}
 | 
					 | 
				
			||||||
                    setProviders={setProviders}
 | 
					 | 
				
			||||||
                    type="chat"
 | 
					 | 
				
			||||||
                  />
 | 
					 | 
				
			||||||
                </div>
 | 
					 | 
				
			||||||
                <div className="flex flex-col gap-2">
 | 
					 | 
				
			||||||
                  {modelProvider.chatModels.some((m) => m.key === 'error') ? (
 | 
					 | 
				
			||||||
                    <div className="flex flex-row items-center gap-2 text-xs lg:text-sm text-red-500 dark:text-red-400 rounded-lg bg-red-50 dark:bg-red-950/20 px-3 py-2 border border-red-200 dark:border-red-900/30">
 | 
					 | 
				
			||||||
                      <AlertCircle size={16} className="shrink-0" />
 | 
					 | 
				
			||||||
                      <span className="break-words">
 | 
					 | 
				
			||||||
                        {
 | 
					 | 
				
			||||||
                          modelProvider.chatModels.find(
 | 
					 | 
				
			||||||
                            (m) => m.key === 'error',
 | 
					 | 
				
			||||||
                          )?.name
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                      </span>
 | 
					 | 
				
			||||||
                    </div>
 | 
					 | 
				
			||||||
                  ) : (
 | 
					 | 
				
			||||||
                    <div className="flex flex-row flex-wrap gap-2">
 | 
					 | 
				
			||||||
                      {modelProvider.chatModels.map((model, index) => (
 | 
					 | 
				
			||||||
                        <div
 | 
					 | 
				
			||||||
                          key={`${modelProvider.id}-chat-${model.key}-${index}`}
 | 
					 | 
				
			||||||
                          className="flex flex-row items-center space-x-1 text-xs lg:text-sm text-black/70 dark:text-white/70 rounded-lg bg-light-secondary dark:bg-dark-secondary px-3 py-1.5"
 | 
					 | 
				
			||||||
                        >
 | 
					 | 
				
			||||||
                          <span>{model.name}</span>
 | 
					 | 
				
			||||||
                          <button
 | 
					 | 
				
			||||||
                            onClick={() => {
 | 
					 | 
				
			||||||
                              handleModelDelete('chat', model.key);
 | 
					 | 
				
			||||||
                            }}
 | 
					 | 
				
			||||||
                          >
 | 
					 | 
				
			||||||
                            <X size={12} />
 | 
					 | 
				
			||||||
                          </button>
 | 
					 | 
				
			||||||
                        </div>
 | 
					 | 
				
			||||||
                      ))}
 | 
					 | 
				
			||||||
                    </div>
 | 
					 | 
				
			||||||
                  )}
 | 
					 | 
				
			||||||
                </div>
 | 
					 | 
				
			||||||
              </div>
 | 
					 | 
				
			||||||
              <div className="flex flex-col gap-y-2">
 | 
					 | 
				
			||||||
                <div className="flex flex-row w-full justify-between items-center">
 | 
					 | 
				
			||||||
                  <p className="text-[11px] lg:text-xs text-black/70 dark:text-white/70">
 | 
					 | 
				
			||||||
                    Embedding models
 | 
					 | 
				
			||||||
                  </p>
 | 
					 | 
				
			||||||
                  <AddModel
 | 
					 | 
				
			||||||
                    providerId={modelProvider.id}
 | 
					 | 
				
			||||||
                    setProviders={setProviders}
 | 
					 | 
				
			||||||
                    type="embedding"
 | 
					 | 
				
			||||||
                  />
 | 
					 | 
				
			||||||
                </div>
 | 
					 | 
				
			||||||
                <div className="flex flex-col gap-2">
 | 
					 | 
				
			||||||
                  {modelProvider.embeddingModels.some(
 | 
					 | 
				
			||||||
                    (m) => m.key === 'error',
 | 
					 | 
				
			||||||
                  ) ? (
 | 
					 | 
				
			||||||
                    <div className="flex flex-row items-center gap-2 text-xs lg:text-sm text-red-500 dark:text-red-400 rounded-lg bg-red-50 dark:bg-red-950/20 px-3 py-2 border border-red-200 dark:border-red-900/30">
 | 
					 | 
				
			||||||
                      <AlertCircle size={16} className="shrink-0" />
 | 
					 | 
				
			||||||
                      <span className="break-words">
 | 
					 | 
				
			||||||
                        {
 | 
					 | 
				
			||||||
                          modelProvider.embeddingModels.find(
 | 
					 | 
				
			||||||
                            (m) => m.key === 'error',
 | 
					 | 
				
			||||||
                          )?.name
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                      </span>
 | 
					 | 
				
			||||||
                    </div>
 | 
					 | 
				
			||||||
                  ) : (
 | 
					 | 
				
			||||||
                    <div className="flex flex-row flex-wrap gap-2">
 | 
					 | 
				
			||||||
                      {modelProvider.embeddingModels.map((model, index) => (
 | 
					 | 
				
			||||||
                        <div
 | 
					 | 
				
			||||||
                          key={`${modelProvider.id}-embedding-${model.key}-${index}`}
 | 
					 | 
				
			||||||
                          className="flex flex-row items-center space-x-1 text-xs lg:text-sm text-black/70 dark:text-white/70 rounded-lg bg-light-secondary dark:bg-dark-secondary px-3 py-1.5"
 | 
					 | 
				
			||||||
                        >
 | 
					 | 
				
			||||||
                          <span>{model.name}</span>
 | 
					 | 
				
			||||||
                          <button
 | 
					 | 
				
			||||||
                            onClick={() => {
 | 
					 | 
				
			||||||
                              handleModelDelete('embedding', model.key);
 | 
					 | 
				
			||||||
                            }}
 | 
					 | 
				
			||||||
                          >
 | 
					 | 
				
			||||||
                            <X size={12} />
 | 
					 | 
				
			||||||
                          </button>
 | 
					 | 
				
			||||||
                        </div>
 | 
					 | 
				
			||||||
                      ))}
 | 
					 | 
				
			||||||
                    </div>
 | 
					 | 
				
			||||||
                  )}
 | 
					 | 
				
			||||||
                </div>
 | 
					 | 
				
			||||||
              </div>
 | 
					 | 
				
			||||||
            </div>
 | 
					 | 
				
			||||||
          </motion.div>
 | 
					 | 
				
			||||||
        )}
 | 
					 | 
				
			||||||
      </AnimatePresence>
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default ModelProvider;
 | 
					 | 
				
			||||||
@@ -1,98 +0,0 @@
 | 
				
			|||||||
import Select from '@/components/ui/Select';
 | 
					 | 
				
			||||||
import { ConfigModelProvider } from '@/lib/config/types';
 | 
					 | 
				
			||||||
import { useChat } from '@/lib/hooks/useChat';
 | 
					 | 
				
			||||||
import { useState } from 'react';
 | 
					 | 
				
			||||||
import { toast } from 'sonner';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const ModelSelect = ({
 | 
					 | 
				
			||||||
  providers,
 | 
					 | 
				
			||||||
  type,
 | 
					 | 
				
			||||||
}: {
 | 
					 | 
				
			||||||
  providers: ConfigModelProvider[];
 | 
					 | 
				
			||||||
  type: 'chat' | 'embedding';
 | 
					 | 
				
			||||||
}) => {
 | 
					 | 
				
			||||||
  const [selectedModel, setSelectedModel] = useState<string>(
 | 
					 | 
				
			||||||
    type === 'chat'
 | 
					 | 
				
			||||||
      ? `${localStorage.getItem('chatModelProviderId')}/${localStorage.getItem('chatModelKey')}`
 | 
					 | 
				
			||||||
      : `${localStorage.getItem('embeddingModelProviderId')}/${localStorage.getItem('embeddingModelKey')}`,
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
  const [loading, setLoading] = useState(false);
 | 
					 | 
				
			||||||
  const { setChatModelProvider, setEmbeddingModelProvider } = useChat();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const handleSave = async (newValue: string) => {
 | 
					 | 
				
			||||||
    setLoading(true);
 | 
					 | 
				
			||||||
    setSelectedModel(newValue);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      if (type === 'chat') {
 | 
					 | 
				
			||||||
        const providerId = newValue.split('/')[0];
 | 
					 | 
				
			||||||
        const modelKey = newValue.split('/').slice(1).join('/');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        localStorage.setItem('chatModelProviderId', providerId);
 | 
					 | 
				
			||||||
        localStorage.setItem('chatModelKey', modelKey);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        setChatModelProvider({
 | 
					 | 
				
			||||||
          providerId: providerId,
 | 
					 | 
				
			||||||
          key: modelKey,
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        const providerId = newValue.split('/')[0];
 | 
					 | 
				
			||||||
        const modelKey = newValue.split('/').slice(1).join('/');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        localStorage.setItem('embeddingModelProviderId', providerId);
 | 
					 | 
				
			||||||
        localStorage.setItem('embeddingModelKey', modelKey);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        setEmbeddingModelProvider({
 | 
					 | 
				
			||||||
          providerId: providerId,
 | 
					 | 
				
			||||||
          key: modelKey,
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    } catch (error) {
 | 
					 | 
				
			||||||
      console.error('Error saving config:', error);
 | 
					 | 
				
			||||||
      toast.error('Failed to save configuration.');
 | 
					 | 
				
			||||||
    } finally {
 | 
					 | 
				
			||||||
      setLoading(false);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return (
 | 
					 | 
				
			||||||
    <section className="rounded-xl border border-light-200 bg-light-primary/80 p-4 lg:p-6 transition-colors dark:border-dark-200 dark:bg-dark-primary/80">
 | 
					 | 
				
			||||||
      <div className="space-y-3 lg:space-y-5">
 | 
					 | 
				
			||||||
        <div>
 | 
					 | 
				
			||||||
          <h4 className="text-sm lg:text-base text-black dark:text-white">
 | 
					 | 
				
			||||||
            Select {type === 'chat' ? 'Chat Model' : 'Embedding Model'}
 | 
					 | 
				
			||||||
          </h4>
 | 
					 | 
				
			||||||
          <p className="text-[11px] lg:text-xs text-black/50 dark:text-white/50">
 | 
					 | 
				
			||||||
            {type === 'chat'
 | 
					 | 
				
			||||||
              ? 'Select the model to use for chat responses'
 | 
					 | 
				
			||||||
              : 'Select the model to use for embeddings'}
 | 
					 | 
				
			||||||
          </p>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
        <Select
 | 
					 | 
				
			||||||
          value={selectedModel}
 | 
					 | 
				
			||||||
          onChange={(event) => handleSave(event.target.value)}
 | 
					 | 
				
			||||||
          options={
 | 
					 | 
				
			||||||
            type === 'chat'
 | 
					 | 
				
			||||||
              ? providers.flatMap((provider) =>
 | 
					 | 
				
			||||||
                  provider.chatModels.map((model) => ({
 | 
					 | 
				
			||||||
                    value: `${provider.id}/${model.key}`,
 | 
					 | 
				
			||||||
                    label: `${provider.name} - ${model.name}`,
 | 
					 | 
				
			||||||
                  })),
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
              : providers.flatMap((provider) =>
 | 
					 | 
				
			||||||
                  provider.embeddingModels.map((model) => ({
 | 
					 | 
				
			||||||
                    value: `${provider.id}/${model.key}`,
 | 
					 | 
				
			||||||
                    label: `${provider.name} - ${model.name}`,
 | 
					 | 
				
			||||||
                  })),
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
          className="!text-xs lg:!text-sm"
 | 
					 | 
				
			||||||
          loading={loading}
 | 
					 | 
				
			||||||
          disabled={loading}
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
    </section>
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default ModelSelect;
 | 
					 | 
				
			||||||
@@ -1,63 +0,0 @@
 | 
				
			|||||||
import React, { useState } from 'react';
 | 
					 | 
				
			||||||
import AddProvider from './AddProviderDialog';
 | 
					 | 
				
			||||||
import {
 | 
					 | 
				
			||||||
  ConfigModelProvider,
 | 
					 | 
				
			||||||
  ModelProviderUISection,
 | 
					 | 
				
			||||||
  UIConfigField,
 | 
					 | 
				
			||||||
} from '@/lib/config/types';
 | 
					 | 
				
			||||||
import ModelProvider from './ModelProvider';
 | 
					 | 
				
			||||||
import ModelSelect from './ModelSelect';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const Models = ({
 | 
					 | 
				
			||||||
  fields,
 | 
					 | 
				
			||||||
  values,
 | 
					 | 
				
			||||||
}: {
 | 
					 | 
				
			||||||
  fields: ModelProviderUISection[];
 | 
					 | 
				
			||||||
  values: ConfigModelProvider[];
 | 
					 | 
				
			||||||
}) => {
 | 
					 | 
				
			||||||
  const [providers, setProviders] = useState<ConfigModelProvider[]>(values);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return (
 | 
					 | 
				
			||||||
    <div className="flex-1 space-y-6 overflow-y-auto py-6">
 | 
					 | 
				
			||||||
      <div className="flex flex-col px-6 gap-y-4">
 | 
					 | 
				
			||||||
        <h3 className="text-xs lg:text-sm text-black/70 dark:text-white/70">
 | 
					 | 
				
			||||||
          Select models
 | 
					 | 
				
			||||||
        </h3>
 | 
					 | 
				
			||||||
        <ModelSelect
 | 
					 | 
				
			||||||
          providers={values.filter((p) =>
 | 
					 | 
				
			||||||
            p.chatModels.some((m) => m.key != 'error'),
 | 
					 | 
				
			||||||
          )}
 | 
					 | 
				
			||||||
          type="chat"
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
        <ModelSelect
 | 
					 | 
				
			||||||
          providers={values.filter((p) =>
 | 
					 | 
				
			||||||
            p.embeddingModels.some((m) => m.key != 'error'),
 | 
					 | 
				
			||||||
          )}
 | 
					 | 
				
			||||||
          type="embedding"
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
      <div className="border-t border-light-200 dark:border-dark-200" />
 | 
					 | 
				
			||||||
      <div className="flex flex-row justify-between items-center px-6 ">
 | 
					 | 
				
			||||||
        <p className="text-xs lg:text-sm text-black/70 dark:text-white/70">
 | 
					 | 
				
			||||||
          Manage model provider
 | 
					 | 
				
			||||||
        </p>
 | 
					 | 
				
			||||||
        <AddProvider modelProviders={fields} setProviders={setProviders} />
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
      <div className="flex flex-col px-6 gap-y-4">
 | 
					 | 
				
			||||||
        {providers.map((provider) => (
 | 
					 | 
				
			||||||
          <ModelProvider
 | 
					 | 
				
			||||||
            key={`provider-${provider.id}`}
 | 
					 | 
				
			||||||
            fields={
 | 
					 | 
				
			||||||
              (fields.find((f) => f.key === provider.type)?.fields ??
 | 
					 | 
				
			||||||
                []) as UIConfigField[]
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            modelProvider={provider}
 | 
					 | 
				
			||||||
            setProviders={setProviders}
 | 
					 | 
				
			||||||
          />
 | 
					 | 
				
			||||||
        ))}
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default Models;
 | 
					 | 
				
			||||||
@@ -1,188 +0,0 @@
 | 
				
			|||||||
import { Dialog, DialogPanel } from '@headlessui/react';
 | 
					 | 
				
			||||||
import { Loader2, Pencil } from 'lucide-react';
 | 
					 | 
				
			||||||
import { useEffect, useState } from 'react';
 | 
					 | 
				
			||||||
import { AnimatePresence, motion } from 'framer-motion';
 | 
					 | 
				
			||||||
import {
 | 
					 | 
				
			||||||
  ConfigModelProvider,
 | 
					 | 
				
			||||||
  StringUIConfigField,
 | 
					 | 
				
			||||||
  UIConfigField,
 | 
					 | 
				
			||||||
} from '@/lib/config/types';
 | 
					 | 
				
			||||||
import { toast } from 'sonner';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const UpdateProvider = ({
 | 
					 | 
				
			||||||
  modelProvider,
 | 
					 | 
				
			||||||
  fields,
 | 
					 | 
				
			||||||
  setProviders,
 | 
					 | 
				
			||||||
}: {
 | 
					 | 
				
			||||||
  fields: UIConfigField[];
 | 
					 | 
				
			||||||
  modelProvider: ConfigModelProvider;
 | 
					 | 
				
			||||||
  setProviders: React.Dispatch<React.SetStateAction<ConfigModelProvider[]>>;
 | 
					 | 
				
			||||||
}) => {
 | 
					 | 
				
			||||||
  const [open, setOpen] = useState(false);
 | 
					 | 
				
			||||||
  const [config, setConfig] = useState<Record<string, any>>({});
 | 
					 | 
				
			||||||
  const [name, setName] = useState(modelProvider.name);
 | 
					 | 
				
			||||||
  const [loading, setLoading] = useState(false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  useEffect(() => {
 | 
					 | 
				
			||||||
    const config: Record<string, any> = {
 | 
					 | 
				
			||||||
      name: modelProvider.name,
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fields.forEach((field) => {
 | 
					 | 
				
			||||||
      config[field.key] =
 | 
					 | 
				
			||||||
        modelProvider.config[field.key] || field.default || '';
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    setConfig(config);
 | 
					 | 
				
			||||||
  }, [fields]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const handleSubmit = async (e: React.FormEvent) => {
 | 
					 | 
				
			||||||
    e.preventDefault();
 | 
					 | 
				
			||||||
    setLoading(true);
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      const res = await fetch(`/api/providers/${modelProvider.id}`, {
 | 
					 | 
				
			||||||
        method: 'PATCH',
 | 
					 | 
				
			||||||
        headers: {
 | 
					 | 
				
			||||||
          'Content-Type': 'application/json',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        body: JSON.stringify({
 | 
					 | 
				
			||||||
          name: name,
 | 
					 | 
				
			||||||
          config: config,
 | 
					 | 
				
			||||||
        }),
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if (!res.ok) {
 | 
					 | 
				
			||||||
        throw new Error('Failed to update provider');
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const data: ConfigModelProvider = (await res.json()).provider;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      setProviders((prev) => {
 | 
					 | 
				
			||||||
        return prev.map((p) => {
 | 
					 | 
				
			||||||
          if (p.id === modelProvider.id) {
 | 
					 | 
				
			||||||
            return data;
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          return p;
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      toast.success('Provider updated successfully.');
 | 
					 | 
				
			||||||
    } catch (error) {
 | 
					 | 
				
			||||||
      console.error('Error updating provider:', error);
 | 
					 | 
				
			||||||
      toast.error('Failed to update provider.');
 | 
					 | 
				
			||||||
    } finally {
 | 
					 | 
				
			||||||
      setLoading(false);
 | 
					 | 
				
			||||||
      setOpen(false);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return (
 | 
					 | 
				
			||||||
    <>
 | 
					 | 
				
			||||||
      <button
 | 
					 | 
				
			||||||
        onClick={(e) => {
 | 
					 | 
				
			||||||
          e.stopPropagation();
 | 
					 | 
				
			||||||
          setOpen(true);
 | 
					 | 
				
			||||||
        }}
 | 
					 | 
				
			||||||
        className="group p-1.5 rounded-md hover:bg-light-200 hover:dark:bg-dark-200 transition-colors group"
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        <Pencil
 | 
					 | 
				
			||||||
          size={14}
 | 
					 | 
				
			||||||
          className="text-black/60 dark:text-white/60 group-hover:text-black group-hover:dark:text-white"
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
      </button>
 | 
					 | 
				
			||||||
      <AnimatePresence>
 | 
					 | 
				
			||||||
        {open && (
 | 
					 | 
				
			||||||
          <Dialog
 | 
					 | 
				
			||||||
            static
 | 
					 | 
				
			||||||
            open={open}
 | 
					 | 
				
			||||||
            onClose={() => setOpen(false)}
 | 
					 | 
				
			||||||
            className="relative z-[60]"
 | 
					 | 
				
			||||||
          >
 | 
					 | 
				
			||||||
            <motion.div
 | 
					 | 
				
			||||||
              initial={{ opacity: 0 }}
 | 
					 | 
				
			||||||
              animate={{ opacity: 1 }}
 | 
					 | 
				
			||||||
              exit={{ opacity: 0 }}
 | 
					 | 
				
			||||||
              transition={{ duration: 0.1 }}
 | 
					 | 
				
			||||||
              className="fixed inset-0 flex w-screen items-center justify-center p-4 bg-black/30 backdrop-blur-sm"
 | 
					 | 
				
			||||||
            >
 | 
					 | 
				
			||||||
              <DialogPanel className="w-full mx-4 lg:w-[600px] max-h-[85vh] flex flex-col border bg-light-primary dark:bg-dark-primary border-light-secondary dark:border-dark-secondary rounded-lg">
 | 
					 | 
				
			||||||
                <form onSubmit={handleSubmit} className="flex flex-col flex-1">
 | 
					 | 
				
			||||||
                  <div className="px-6 pt-6 pb-4">
 | 
					 | 
				
			||||||
                    <h3 className="text-black/90 dark:text-white/90 font-medium">
 | 
					 | 
				
			||||||
                      Update provider
 | 
					 | 
				
			||||||
                    </h3>
 | 
					 | 
				
			||||||
                  </div>
 | 
					 | 
				
			||||||
                  <div className="border-t border-light-200 dark:border-dark-200" />
 | 
					 | 
				
			||||||
                  <div className="flex-1 overflow-y-auto px-6 py-4">
 | 
					 | 
				
			||||||
                    <div className="flex flex-col space-y-4">
 | 
					 | 
				
			||||||
                      <div
 | 
					 | 
				
			||||||
                        key="name"
 | 
					 | 
				
			||||||
                        className="flex flex-col items-start space-y-2"
 | 
					 | 
				
			||||||
                      >
 | 
					 | 
				
			||||||
                        <label className="text-xs text-black/70 dark:text-white/70">
 | 
					 | 
				
			||||||
                          Name*
 | 
					 | 
				
			||||||
                        </label>
 | 
					 | 
				
			||||||
                        <input
 | 
					 | 
				
			||||||
                          value={name}
 | 
					 | 
				
			||||||
                          onChange={(event) => setName(event.target.value)}
 | 
					 | 
				
			||||||
                          className="w-full rounded-lg border border-light-200 dark:border-dark-200 bg-light-primary dark:bg-dark-primary px-4 py-3 pr-10 text-sm text-black/80 dark:text-white/80 placeholder:text-black/40 dark:placeholder:text-white/40 focus-visible:outline-none focus-visible:border-light-300 dark:focus-visible:border-dark-300 transition-colors disabled:cursor-not-allowed disabled:opacity-60"
 | 
					 | 
				
			||||||
                          placeholder={'Provider Name'}
 | 
					 | 
				
			||||||
                          type="text"
 | 
					 | 
				
			||||||
                          required={true}
 | 
					 | 
				
			||||||
                        />
 | 
					 | 
				
			||||||
                      </div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                      {fields.map((field: UIConfigField) => (
 | 
					 | 
				
			||||||
                        <div
 | 
					 | 
				
			||||||
                          key={field.key}
 | 
					 | 
				
			||||||
                          className="flex flex-col items-start space-y-2"
 | 
					 | 
				
			||||||
                        >
 | 
					 | 
				
			||||||
                          <label className="text-xs text-black/70 dark:text-white/70">
 | 
					 | 
				
			||||||
                            {field.name}
 | 
					 | 
				
			||||||
                            {field.required && '*'}
 | 
					 | 
				
			||||||
                          </label>
 | 
					 | 
				
			||||||
                          <input
 | 
					 | 
				
			||||||
                            value={config[field.key] ?? field.default ?? ''}
 | 
					 | 
				
			||||||
                            onChange={(event) =>
 | 
					 | 
				
			||||||
                              setConfig((prev) => ({
 | 
					 | 
				
			||||||
                                ...prev,
 | 
					 | 
				
			||||||
                                [field.key]: event.target.value,
 | 
					 | 
				
			||||||
                              }))
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
                            className="w-full rounded-lg border border-light-200 dark:border-dark-200 bg-light-primary dark:bg-dark-primary px-4 py-3 pr-10 text-sm text-black/80 dark:text-white/80 placeholder:text-black/40 dark:placeholder:text-white/40 focus-visible:outline-none focus-visible:border-light-300 dark:focus-visible:border-dark-300 transition-colors disabled:cursor-not-allowed disabled:opacity-60"
 | 
					 | 
				
			||||||
                            placeholder={
 | 
					 | 
				
			||||||
                              (field as StringUIConfigField).placeholder
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
                            type="text"
 | 
					 | 
				
			||||||
                            required={field.required}
 | 
					 | 
				
			||||||
                          />
 | 
					 | 
				
			||||||
                        </div>
 | 
					 | 
				
			||||||
                      ))}
 | 
					 | 
				
			||||||
                    </div>
 | 
					 | 
				
			||||||
                  </div>
 | 
					 | 
				
			||||||
                  <div className="border-t border-light-200 dark:border-dark-200" />
 | 
					 | 
				
			||||||
                  <div className="px-6 py-4 flex justify-end">
 | 
					 | 
				
			||||||
                    <button
 | 
					 | 
				
			||||||
                      type="submit"
 | 
					 | 
				
			||||||
                      disabled={loading}
 | 
					 | 
				
			||||||
                      className="px-4 py-2 rounded-lg text-sm bg-sky-500 text-white font-medium disabled:opacity-85 hover:opacity-85 active:scale-95 transition duration-200"
 | 
					 | 
				
			||||||
                    >
 | 
					 | 
				
			||||||
                      {loading ? (
 | 
					 | 
				
			||||||
                        <Loader2 className="animate-spin" size={16} />
 | 
					 | 
				
			||||||
                      ) : (
 | 
					 | 
				
			||||||
                        'Update Provider'
 | 
					 | 
				
			||||||
                      )}
 | 
					 | 
				
			||||||
                    </button>
 | 
					 | 
				
			||||||
                  </div>
 | 
					 | 
				
			||||||
                </form>
 | 
					 | 
				
			||||||
              </DialogPanel>
 | 
					 | 
				
			||||||
            </motion.div>
 | 
					 | 
				
			||||||
          </Dialog>
 | 
					 | 
				
			||||||
        )}
 | 
					 | 
				
			||||||
      </AnimatePresence>
 | 
					 | 
				
			||||||
    </>
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default UpdateProvider;
 | 
					 | 
				
			||||||