Skip to content

Commit

Permalink
feat(core): support ai network search
Browse files Browse the repository at this point in the history
  • Loading branch information
akumatus committed Jan 8, 2025
1 parent 69e73af commit b09f519
Show file tree
Hide file tree
Showing 50 changed files with 1,170 additions and 172 deletions.
1 change: 1 addition & 0 deletions .github/actions/copilot-test/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ runs:
DEV_SERVER_URL: http://localhost:8080
COPILOT_OPENAI_API_KEY: ${{ inputs.openai-key }}
COPILOT_FAL_API_KEY: ${{ inputs.fal-key }}
COPILOT_PERPLEXITY_API_KEY: ${{ inputs.perplexity-key }}

- name: Upload test results
if: ${{ failure() }}
Expand Down
2 changes: 2 additions & 0 deletions .github/actions/deploy/deploy.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const {
METRICS_CUSTOMER_IO_TOKEN,
COPILOT_OPENAI_API_KEY,
COPILOT_FAL_API_KEY,
COPILOT_PERPLEXITY_API_KEY,
COPILOT_UNSPLASH_API_KEY,
MAILER_SENDER,
MAILER_USER,
Expand Down Expand Up @@ -147,6 +148,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.perplexity.key="${COPILOT_PERPLEXITY_API_KEY}"`,
`--set-string graphql.app.copilot.unsplash.key="${COPILOT_UNSPLASH_API_KEY}"`,
`--set-string graphql.app.mailer.sender="${MAILER_SENDER}"`,
`--set-string graphql.app.mailer.user="${MAILER_USER}"`,
Expand Down
5 changes: 5 additions & 0 deletions .github/helm/affine/charts/graphql/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,11 @@ spec:
secretKeyRef:
name: "{{ .Values.app.copilot.secretName }}"
key: falSecret
- name: COPILOT_PERPLEXITY_API_KEY
valueFrom:
secretKeyRef:
name: "{{ .Values.app.copilot.secretName }}"
key: perplexitySecret
- name: COPILOT_UNSPLASH_API_KEY
valueFrom:
secretKeyRef:
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/build-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,7 @@ jobs:
CARGO_TARGET_DIR: '${{ github.workspace }}/target'
COPILOT_OPENAI_API_KEY: ${{ secrets.COPILOT_OPENAI_API_KEY }}
COPILOT_FAL_API_KEY: ${{ secrets.COPILOT_FAL_API_KEY }}
COPILOT_PERPLEXITY_API_KEY: ${{ secrets.COPILOT_PERPLEXITY_API_KEY }}

- name: Upload server test coverage results
if: ${{ steps.check-blocksuite-update.outputs.skip != 'true' || steps.apifilter.outputs.changed == 'true' }}
Expand Down Expand Up @@ -619,6 +620,7 @@ jobs:
script: yarn affine @affine-test/affine-cloud-copilot e2e --forbid-only --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
openai-key: ${{ secrets.COPILOT_OPENAI_API_KEY }}
fal-key: ${{ secrets.COPILOT_FAL_API_KEY }}
perplexity-key: ${{ secrets.COPILOT_PERPLEXITY_API_KEY }}

server-e2e-test:
name: ${{ matrix.tests.name }}
Expand Down Expand Up @@ -703,6 +705,7 @@ jobs:
DEV_SERVER_URL: http://localhost:8080
COPILOT_OPENAI_API_KEY: 1
COPILOT_FAL_API_KEY: 1
COPILOT_PERPLEXITY_API_KEY: 1

- name: Upload test results
if: ${{ failure() }}
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/copilot-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ jobs:
CARGO_TARGET_DIR: '${{ github.workspace }}/target'
COPILOT_OPENAI_API_KEY: ${{ secrets.COPILOT_OPENAI_API_KEY }}
COPILOT_FAL_API_KEY: ${{ secrets.COPILOT_FAL_API_KEY }}
COPILOT_PERPLEXITY_API_KEY: ${{ secrets.COPILOT_PERPLEXITY_API_KEY }}

- name: Upload server test coverage results
uses: codecov/codecov-action@v5
Expand Down Expand Up @@ -147,6 +148,7 @@ jobs:
script: yarn affine @affine-test/affine-cloud-copilot e2e --forbid-only --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
openai-key: ${{ secrets.COPILOT_OPENAI_API_KEY }}
fal-key: ${{ secrets.COPILOT_FAL_API_KEY }}
perplexity-key: ${{ secrets.COPILOT_PERPLEXITY_API_KEY }}

test-done:
needs:
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,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_PERPLEXITY_API_KEY: ${{ secrets.COPILOT_PERPLEXITY_API_KEY }}
COPILOT_UNSPLASH_API_KEY: ${{ secrets.COPILOT_UNSPLASH_API_KEY }}
METRICS_CUSTOMER_IO_TOKEN: ${{ secrets.METRICS_CUSTOMER_IO_TOKEN }}
MAILER_SENDER: ${{ secrets.OAUTH_EMAIL_SENDER }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export class AffineAIPanelWidget extends WidgetComponent {
box-sizing: border-box;
width: 100%;
height: fit-content;
padding: 8px 0;
padding: 10px 0;
}
.ai-panel-container:not(:has(ai-panel-generating)) {
Expand Down Expand Up @@ -474,6 +474,7 @@ export class AffineAIPanelWidget extends WidgetComponent {
.onBlur=${this.discard}
.onFinish=${this._inputFinish}
.onInput=${this.onInput}
.networkSearchConfig=${config.networkSearchConfig}
></ai-panel-input>`,
],
[
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { AIStarIcon } from '@blocksuite/affine-components/icons';
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
import { stopPropagation } from '@blocksuite/affine-shared/utils';
import { WithDisposable } from '@blocksuite/global/utils';
import { SendIcon } from '@blocksuite/icons/lit';
import { SignalWatcher, WithDisposable } from '@blocksuite/global/utils';
import { PublishIcon, SendIcon } from '@blocksuite/icons/lit';
import { css, html, LitElement, nothing } from 'lit';
import { property, query, state } from 'lit/decorators.js';

export class AIPanelInput extends WithDisposable(LitElement) {
import type { AINetworkSearchConfig } from '../../type';

export class AIPanelInput extends SignalWatcher(WithDisposable(LitElement)) {
static override styles = css`
:host {
width: 100%;
Expand All @@ -20,8 +23,9 @@ export class AIPanelInput extends WithDisposable(LitElement) {
background: var(--affine-background-overlay-panel-color);
}
.icon {
.star {
display: flex;
padding: 2px;
align-items: center;
}
Expand Down Expand Up @@ -66,22 +70,36 @@ export class AIPanelInput extends WithDisposable(LitElement) {
display: flex;
align-items: center;
padding: 2px;
gap: 10px;
gap: 4px;
border-radius: 4px;
background: var(--affine-black-10, rgba(0, 0, 0, 0.1));
background: ${unsafeCSSVarV2('icon/disable')};
svg {
width: 16px;
height: 16px;
color: var(--affine-pure-white, #fff);
width: 20px;
height: 20px;
color: ${unsafeCSSVarV2('button/pureWhiteText')};
}
}
.arrow[data-active] {
background: var(--affine-brand-color, #1e96eb);
background: ${unsafeCSSVarV2('icon/activated')};
}
.arrow[data-active]:hover {
cursor: pointer;
}
.network {
display: flex;
align-items: center;
padding: 2px;
gap: 4px;
cursor: pointer;
svg {
width: 20px;
height: 20px;
color: ${unsafeCSSVarV2('icon/primary')};
}
}
.network[data-active='true'] svg {
color: ${unsafeCSSVarV2('icon/activated')};
}
`;

private readonly _onInput = () => {
Expand All @@ -101,22 +119,32 @@ export class AIPanelInput extends WithDisposable(LitElement) {

private readonly _onKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Enter' && !e.shiftKey && !e.isComposing) {
e.preventDefault();
this._sendToAI();
this._sendToAI(e);
}
};

private readonly _sendToAI = () => {
private readonly _sendToAI = (e: MouseEvent | KeyboardEvent) => {
e.preventDefault();
e.stopPropagation();

const value = this.textarea.value.trim();
if (value.length === 0) return;

this.onFinish?.(value);
this.remove();
};

private readonly _toggleNetworkSearch = (e: MouseEvent) => {
e.preventDefault();
e.stopPropagation();

const enable = this.networkSearchConfig.enabled.value;
this.networkSearchConfig.setEnabled(!enable);
};

override render() {
return html`<div class="root">
<div class="icon">${AIStarIcon}</div>
<div class="star">${AIStarIcon}</div>
<div class="textarea-container">
<textarea
placeholder="What are your thoughts?"
Expand All @@ -131,6 +159,21 @@ export class AIPanelInput extends WithDisposable(LitElement) {
@paste=${stopPropagation}
@keyup=${stopPropagation}
></textarea>
${this.networkSearchConfig.visible.value
? html`
<div
class="network"
data-active=${!!this.networkSearchConfig.enabled.value}
@click=${this._toggleNetworkSearch}
@pointerdown=${stopPropagation}
>
${PublishIcon()}
<affine-tooltip .offset=${12}
>Toggle Network Search</affine-tooltip
>
</div>
`
: nothing}
<div
class="arrow"
@click=${this._sendToAI}
Expand All @@ -157,6 +200,9 @@ export class AIPanelInput extends WithDisposable(LitElement) {
@state()
private accessor _hasContent = false;

@property({ attribute: false })
accessor networkSearchConfig!: AINetworkSearchConfig;

@property({ attribute: false })
accessor onFinish: ((input: string) => void) | undefined = undefined;

Expand Down
9 changes: 8 additions & 1 deletion blocksuite/blocks/src/root-block/widgets/ai-panel/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type {
AIError,
AIItemGroupConfig,
} from '@blocksuite/affine-components/ai-item';
import type { Signal } from '@preact/signals-core';
import type { nothing, TemplateResult } from 'lit';

export interface CopyConfig {
Expand All @@ -28,6 +29,12 @@ export interface AIPanelGeneratingConfig {
stages?: string[];
}

export interface AINetworkSearchConfig {
visible: Signal<boolean | undefined>;
enabled: Signal<boolean | undefined>;
setEnabled: (state: boolean) => void;
}

export interface AffineAIPanelWidgetConfig {
answerRenderer: (
answer: string,
Expand All @@ -44,10 +51,10 @@ export interface AffineAIPanelWidgetConfig {
finishStateConfig: AIPanelAnswerConfig;
generatingStateConfig: AIPanelGeneratingConfig;
errorStateConfig: AIPanelErrorConfig;
networkSearchConfig: AINetworkSearchConfig;
hideCallback?: () => void;
discardCallback?: () => void;
inputCallback?: (input: string) => void;

copy?: CopyConfig;
}

Expand Down
1 change: 1 addition & 0 deletions packages/backend/server/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# REDIS_SERVER_HOST=localhost
# COPILOT_FAL_API_KEY=YOUR_KEY
# COPILOT_OPENAI_API_KEY=YOUR_KEY
# COPILOT_PERPLEXITY_API_KEY=YOUR_KEY

# MAILER_HOST=127.0.0.1
# MAILER_PORT=1025
Expand Down
1 change: 1 addition & 0 deletions packages/backend/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"@socket.io/redis-adapter": "^8.3.0",
"cookie-parser": "^1.4.7",
"dotenv": "^16.4.7",
"eventsource-parser": "^3.0.0",
"express": "^4.21.2",
"fast-xml-parser": "^4.5.0",
"get-stream": "^9.0.1",
Expand Down
1 change: 1 addition & 0 deletions packages/backend/server/src/config/affine.env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ AFFiNE.ENV_MAP = {
CAPTCHA_TURNSTILE_SECRET: ['plugins.captcha.turnstile.secret', 'string'],
COPILOT_OPENAI_API_KEY: 'plugins.copilot.openai.apiKey',
COPILOT_FAL_API_KEY: 'plugins.copilot.fal.apiKey',
COPILOT_PERPLEXITY_API_KEY: 'plugins.copilot.perplexity.apiKey',
COPILOT_UNSPLASH_API_KEY: 'plugins.copilot.unsplashKey',
REDIS_SERVER_HOST: 'redis.host',
REDIS_SERVER_PORT: ['redis.port', 'int'],
Expand Down
2 changes: 2 additions & 0 deletions packages/backend/server/src/plugins/copilot/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import type { ClientOptions as OpenAIClientOptions } from 'openai';
import { defineStartupConfig, ModuleConfig } from '../../base/config';
import { StorageConfig } from '../../base/storage/config';
import type { FalConfig } from './providers/fal';
import { PerplexityConfig } from './providers/perplexity';

export interface CopilotStartupConfigurations {
openai?: OpenAIClientOptions;
fal?: FalConfig;
perplexity?: PerplexityConfig;
test?: never;
unsplashKey?: string;
storage: StorageConfig;
Expand Down
2 changes: 2 additions & 0 deletions packages/backend/server/src/plugins/copilot/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
CopilotProviderService,
FalProvider,
OpenAIProvider,
PerplexityProvider,
registerCopilotProvider,
} from './providers';
import {
Expand All @@ -26,6 +27,7 @@ import { CopilotWorkflowExecutors, CopilotWorkflowService } from './workflow';

registerCopilotProvider(FalProvider);
registerCopilotProvider(OpenAIProvider);
registerCopilotProvider(PerplexityProvider);

@Plugin({
name: 'copilot',
Expand Down
5 changes: 5 additions & 0 deletions packages/backend/server/src/plugins/copilot/prompt/prompts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -952,6 +952,11 @@ const chat: Prompt[] = [
},
],
},
{
name: 'Search With AFFiNE AI',
model: 'llama-3.1-sonar-small-128k-online',
messages: [],
},
// use for believer plan
{
name: 'Chat With AFFiNE AI - Believer',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,7 @@ export class CopilotProviderService {
if (!this.cachedProviders.has(provider)) {
this.cachedProviders.set(provider, this.create(provider));
}

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return this.cachedProviders.get(provider)!;
return this.cachedProviders.get(provider) as CopilotProvider;
}

async getProviderByCapability<C extends CopilotCapability>(
Expand Down Expand Up @@ -196,3 +194,4 @@ export class CopilotProviderService {

export { FalProvider } from './fal';
export { OpenAIProvider } from './openai';
export { PerplexityProvider } from './perplexity';
Loading

0 comments on commit b09f519

Please sign in to comment.