From 677c4711df66ac05eee57de2a8d45d06f7ac9c9c Mon Sep 17 00:00:00 2001 From: darkskygit Date: Tue, 16 Apr 2024 13:33:07 +0000 Subject: [PATCH] feat: unsplash api proxy (#6572) --- .github/actions/deploy/deploy.mjs | 2 + .../graphql/templates/copilot-secret.yaml | 1 + .../charts/graphql/templates/deployment.yaml | 5 ++ .github/workflows/deploy.yml | 1 + .../backend/server/src/config/affine.env.ts | 1 + .../server/src/plugins/copilot/controller.ts | 46 +++++++++++++++++-- .../server/src/plugins/copilot/types.ts | 6 +-- 7 files changed, 56 insertions(+), 6 deletions(-) diff --git a/.github/actions/deploy/deploy.mjs b/.github/actions/deploy/deploy.mjs index e1583cf540..8fad47eb68 100644 --- a/.github/actions/deploy/deploy.mjs +++ b/.github/actions/deploy/deploy.mjs @@ -16,6 +16,7 @@ const { CAPTCHA_TURNSTILE_SECRET, COPILOT_OPENAI_API_KEY, COPILOT_FAL_API_KEY, + COPILOT_UNSPLASH_API_KEY, MAILER_SENDER, MAILER_USER, MAILER_PASSWORD, @@ -103,6 +104,7 @@ const createHelmCommand = ({ isDryRun }) => { `--set graphql.app.copilot.enabled=true`, `--set-string graphql.app.copilot.openai.key="${COPILOT_OPENAI_API_KEY}"`, `--set-string graphql.app.copilot.fal.key="${COPILOT_FAL_API_KEY}"`, + `--set-string graphql.app.copilot.unsplash.key="${COPILOT_UNSPLASH_API_KEY}"`, `--set graphql.app.objectStorage.r2.enabled=true`, `--set-string graphql.app.objectStorage.r2.accountId="${R2_ACCOUNT_ID}"`, `--set-string graphql.app.objectStorage.r2.accessKeyId="${R2_ACCESS_KEY_ID}"`, diff --git a/.github/helm/affine/charts/graphql/templates/copilot-secret.yaml b/.github/helm/affine/charts/graphql/templates/copilot-secret.yaml index 26858e63dc..c4d93dc773 100644 --- a/.github/helm/affine/charts/graphql/templates/copilot-secret.yaml +++ b/.github/helm/affine/charts/graphql/templates/copilot-secret.yaml @@ -7,4 +7,5 @@ type: Opaque data: openaiSecret: {{ .Values.app.copilot.openai.key | b64enc }} falSecret: {{ .Values.app.copilot.fal.key | b64enc }} + unsplashSecret: {{ .Values.app.copilot.unsplash.key | b64enc }} {{- end }} diff --git a/.github/helm/affine/charts/graphql/templates/deployment.yaml b/.github/helm/affine/charts/graphql/templates/deployment.yaml index 56f575206d..580e35e5f8 100644 --- a/.github/helm/affine/charts/graphql/templates/deployment.yaml +++ b/.github/helm/affine/charts/graphql/templates/deployment.yaml @@ -159,6 +159,11 @@ spec: secretKeyRef: name: "{{ .Values.app.copilot.secretName }}" key: falSecret + - name: COPILOT_UNSPLASH_API_KEY + valueFrom: + secretKeyRef: + name: "{{ .Values.app.copilot.secretName }}" + key: unsplashSecret {{ end }} {{ if .Values.app.oauth.google.enabled }} - name: OAUTH_GOOGLE_ENABLED diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index ec233b55fa..b453c5af9c 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -136,6 +136,7 @@ jobs: CAPTCHA_TURNSTILE_SECRET: ${{ secrets.CAPTCHA_TURNSTILE_SECRET }} COPILOT_OPENAI_API_KEY: ${{ secrets.COPILOT_OPENAI_API_KEY }} COPILOT_FAL_API_KEY: ${{ secrets.COPILOT_FAL_API_KEY }} + COPILOT_UNSPLASH_API_KEY: ${{ secrets.COPILOT_UNSPLASH_API_KEY }} MAILER_SENDER: ${{ secrets.OAUTH_EMAIL_SENDER }} MAILER_USER: ${{ secrets.OAUTH_EMAIL_LOGIN }} MAILER_PASSWORD: ${{ secrets.OAUTH_EMAIL_PASSWORD }} diff --git a/packages/backend/server/src/config/affine.env.ts b/packages/backend/server/src/config/affine.env.ts index c393f6b89d..a49d58590e 100644 --- a/packages/backend/server/src/config/affine.env.ts +++ b/packages/backend/server/src/config/affine.env.ts @@ -21,6 +21,7 @@ AFFiNE.ENV_MAP = { THROTTLE_LIMIT: ['rateLimiter.limit', 'int'], COPILOT_OPENAI_API_KEY: 'plugins.copilot.openai.apiKey', COPILOT_FAL_API_KEY: 'plugins.copilot.fal.apiKey', + COPILOT_UNSPLASH_API_KEY: 'plugins.copilot.unsplashKey', REDIS_SERVER_HOST: 'plugins.redis.host', REDIS_SERVER_PORT: ['plugins.redis.port', 'int'], REDIS_SERVER_USER: 'plugins.redis.username', diff --git a/packages/backend/server/src/plugins/copilot/controller.ts b/packages/backend/server/src/plugins/copilot/controller.ts index d1b9fb9822..bb5d128b6d 100644 --- a/packages/backend/server/src/plugins/copilot/controller.ts +++ b/packages/backend/server/src/plugins/copilot/controller.ts @@ -6,8 +6,10 @@ import { Param, Query, Req, + Res, Sse, } from '@nestjs/common'; +import type { Request, Response } from 'express'; import { concatMap, connect, @@ -21,6 +23,7 @@ import { } from 'rxjs'; import { CurrentUser } from '../../core/auth/current-user'; +import { Config } from '../../fundamentals'; import { CopilotProviderService } from './providers'; import { ChatSession, ChatSessionService } from './session'; import { CopilotCapability } from './types'; @@ -34,6 +37,7 @@ export interface ChatEvent { @Controller('/api/copilot') export class CopilotController { constructor( + private readonly config: Config, private readonly chatSession: ChatSessionService, private readonly provider: CopilotProviderService ) {} @@ -78,6 +82,12 @@ export class CopilotController { return session; } + private getSignal(req: Request) { + const controller = new AbortController(); + req.on('close', () => controller.abort()); + return controller.signal; + } + @Get('/chat/:sessionId') async chat( @CurrentUser() user: CurrentUser, @@ -111,7 +121,7 @@ export class CopilotController { session.finish(params), session.model, { - signal: req.signal, + signal: this.getSignal(req), user: user.id, } ); @@ -161,7 +171,7 @@ export class CopilotController { delete params.messageId; return from( provider.generateTextStream(session.finish(params), session.model, { - signal: req.signal, + signal: this.getSignal(req), user: user.id, }) ).pipe( @@ -222,7 +232,7 @@ export class CopilotController { delete params.messageId; return from( provider.generateImagesStream(session.finish(params), session.model, { - signal: req.signal, + signal: this.getSignal(req), user: user.id, }) ).pipe( @@ -254,4 +264,34 @@ export class CopilotController { ) ); } + + @Get('/unsplash/photos') + async unsplashPhotos( + @Req() req: Request, + @Res() res: Response, + @Query() params: Record + ) { + const { unsplashKey } = this.config.plugins.copilot || {}; + if (!unsplashKey) { + throw new InternalServerErrorException('Unsplash key is not configured'); + } + + const query = new URLSearchParams(params); + const response = await fetch( + `https://api.unsplash.com/search/photos?${query}`, + { + headers: { Authorization: `Client-ID ${unsplashKey}` }, + signal: this.getSignal(req), + } + ); + + res.set({ + 'Content-Type': response.headers.get('Content-Type'), + 'Content-Length': response.headers.get('Content-Length'), + 'X-Ratelimit-Limit': response.headers.get('X-Ratelimit-Limit'), + 'X-Ratelimit-Remaining': response.headers.get('X-Ratelimit-Remaining'), + }); + + res.status(response.status).send(await response.json()); + } } diff --git a/packages/backend/server/src/plugins/copilot/types.ts b/packages/backend/server/src/plugins/copilot/types.ts index 450cbff315..dd529f6ee5 100644 --- a/packages/backend/server/src/plugins/copilot/types.ts +++ b/packages/backend/server/src/plugins/copilot/types.ts @@ -9,12 +9,12 @@ import { import { z } from 'zod'; import type { ChatPrompt } from './prompt'; +import type { FalConfig } from './providers/fal'; export interface CopilotConfig { openai: OpenAIClientOptions; - fal: { - apiKey: string; - }; + fal: FalConfig; + unsplashKey: string; } export enum AvailableModels {