Compare commits

...

6 Commits

Author SHA1 Message Date
Wu Yue
a7185e419c
feat(core): add ai tool call error type and ui (#12941)
<img width="775" alt="截屏2025-06-26 16 17 05"
src="https://github.com/user-attachments/assets/ed6bcae3-94af-4eb1-81e8-710f36ef5e46"
/>


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
  * Introduced a tool to crawl web pages and extract key information.
* Added a visual component to display tool call failures in the AI
interface.
* Enhanced error reporting for document and web search tools with
structured error messages.

* **Improvements**
* Updated error handling across AI tools and components for more
consistent and informative feedback.
* Default values added for tool card components to improve reliability
and display.

* **Bug Fixes**
* Improved handling of error and empty states in web crawl and web
search result displays.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-26 09:38:19 +00:00
Wu Yue
2171d1bfe2
feat(core): add ai playground feature flag and remove model switch feature flag (#12934)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Added support for the "AI Playground" experimental feature, including
new settings and localization entries.

* **Refactor**
* Renamed configuration and service references from "Model Switch" to
"Playground" across the AI chat and playground interfaces.
* Updated feature flag from "enable_ai_model_switch" to
"enable_ai_playground" for consistency.

* **Bug Fixes**
* The "Model" submenu in AI chat preferences is now always visible,
simplifying menu options.

* **Chores**
  * Removed outdated Claude model options from the chat prompt.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-26 09:04:57 +00:00
DarkSky
5e193b58c0
fix(server): allow fork empty session in playground (#12940)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Improved session forking by allowing sessions to be forked even when
no messages are provided, preventing unnecessary errors.
* Reduced unnecessary database operations by only updating messages when
there are messages to update.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-26 09:04:25 +00:00
EYHN
eef2e05d83
feat(ios): update code sign identity script (#12942) 2025-06-26 08:59:01 +00:00
EYHN
f3a2a75743
feat(ios): update code sign identity script (#12938)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Chores**
* Improved the iOS release workflow to include automated version setup
and code signing identity updates during the release process.
* Introduced a script to automatically update iOS project code signing
settings for enhanced reliability.
* Enhanced versioning script to update the iOS marketing version in the
project file.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-26 08:05:14 +00:00
L-Sun
a6edb6192f
fix(editor): improve performence of link card render (#12937)
Close
[BS-3097](https://linear.app/affine-design/issue/BS-3607/linkdoccard-重复渲染导致卡顿)

![CleanShot 2025-06-26 at 14 58
54](https://github.com/user-attachments/assets/2787f88b-3596-4a34-a9a8-91966b83418d)


#### PR Dependency Tree


* **PR #12937** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Refactor**
* Improved the handling of updates for embedded linked documents,
reducing unnecessary reloads when the document list changes.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-26 07:13:15 +00:00
36 changed files with 423 additions and 208 deletions

View File

@ -83,6 +83,13 @@ jobs:
- build-ios-web - build-ios-web
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Setup Version
uses: ./.github/actions/setup-version
with:
app-version: ${{ inputs.app-version }}
- name: 'Update Code Sign Identity'
shell: bash
run: ./packages/frontend/apps/ios/update_code_sign_identity.sh
- name: Download mobile artifact - name: Download mobile artifact
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:

View File

@ -495,14 +495,6 @@ export class EmbedLinkedDocBlockComponent extends EmbedBlockComponent<EmbedLinke
const linkedDoc = this.linkedDoc; const linkedDoc = this.linkedDoc;
if (linkedDoc) { if (linkedDoc) {
this.disposables.add(
linkedDoc.workspace.slots.docListUpdated.subscribe(() => {
this._load().catch(e => {
console.error(e);
this.isError = true;
});
})
);
// Should throttle the blockUpdated event to avoid too many re-renders // Should throttle the blockUpdated event to avoid too many re-renders
// Because the blockUpdated event is triggered too frequently at some cases // Because the blockUpdated event is triggered too frequently at some cases
this.disposables.add( this.disposables.add(

View File

@ -188,11 +188,6 @@ export class CopilotSessionModel extends BaseModel {
@Transactional() @Transactional()
async fork(options: ForkSessionOptions): Promise<string> { async fork(options: ForkSessionOptions): Promise<string> {
if (!options.messages?.length) {
throw new CopilotSessionInvalidInput(
'Cannot fork session without messages'
);
}
if (options.pinned) { if (options.pinned) {
await this.unpin(options.workspaceId, options.userId); await this.unpin(options.workspaceId, options.userId);
} }
@ -203,12 +198,15 @@ export class CopilotSessionModel extends BaseModel {
...forkedState, ...forkedState,
messages: [], messages: [],
}); });
if (options.messages.length) {
// save message // save message
await this.models.copilotSession.updateMessages({ await this.models.copilotSession.updateMessages({
...forkedState, ...forkedState,
sessionId, sessionId,
messages, messages,
}); });
}
return sessionId; return sessionId;
} }

View File

@ -1650,10 +1650,6 @@ const CHAT_PROMPT: Omit<Prompt, 'name'> = {
'gpt-4.1', 'gpt-4.1',
'o3', 'o3',
'o4-mini', 'o4-mini',
'claude-opus-4-20250514',
'claude-sonnet-4-20250514',
'claude-3-7-sonnet-20250219',
'claude-3-5-sonnet-20241022',
'gemini-2.5-flash', 'gemini-2.5-flash',
'gemini-2.5-pro', 'gemini-2.5-pro',
'claude-opus-4@20250514', 'claude-opus-4@20250514',

View File

@ -390,19 +390,19 @@ export interface CustomAITools extends ToolSet {
type ChunkType = TextStreamPart<CustomAITools>['type']; type ChunkType = TextStreamPart<CustomAITools>['type'];
export function parseUnknownError(error: unknown) { export function toError(error: unknown): Error {
if (typeof error === 'string') { if (typeof error === 'string') {
throw new Error(error); return new Error(error);
} else if (error instanceof Error) { } else if (error instanceof Error) {
throw error; return error;
} else if ( } else if (
typeof error === 'object' && typeof error === 'object' &&
error !== null && error !== null &&
'message' in error 'message' in error
) { ) {
throw new Error(String(error.message)); return new Error(String(error.message));
} else { } else {
throw new Error(JSON.stringify(error)); return new Error(JSON.stringify(error));
} }
} }
@ -483,8 +483,7 @@ export class TextStreamParser {
break; break;
} }
case 'error': { case 'error': {
parseUnknownError(chunk.error); throw toError(chunk.error);
break;
} }
} }
this.lastType = chunk.type; this.lastType = chunk.type;
@ -550,8 +549,7 @@ export class StreamObjectParser {
return chunk; return chunk;
} }
case 'error': { case 'error': {
parseUnknownError(chunk.error); throw toError(chunk.error);
return null;
} }
default: { default: {
return null; return null;

View File

@ -4,6 +4,7 @@ import { z } from 'zod';
import type { AccessController } from '../../../core/permission'; import type { AccessController } from '../../../core/permission';
import type { IndexerService, SearchDoc } from '../../indexer'; import type { IndexerService, SearchDoc } from '../../indexer';
import type { CopilotChatOptions } from '../providers'; import type { CopilotChatOptions } from '../providers';
import { toolError } from './error';
export const buildDocKeywordSearchGetter = ( export const buildDocKeywordSearchGetter = (
ac: AccessController, ac: AccessController,
@ -56,8 +57,8 @@ export const createDocKeywordSearchTool = (
createdByUser: doc.createdByUser, createdByUser: doc.createdByUser,
updatedByUser: doc.updatedByUser, updatedByUser: doc.updatedByUser,
})); }));
} catch { } catch (e: any) {
return 'Failed to search documents.'; return toolError('Doc Keyword Search Failed', e.message);
} }
}, },
}); });

View File

@ -5,6 +5,7 @@ import type { AccessController } from '../../../core/permission';
import type { ChunkSimilarity } from '../../../models'; import type { ChunkSimilarity } from '../../../models';
import type { CopilotContextService } from '../context'; import type { CopilotContextService } from '../context';
import type { CopilotChatOptions } from '../providers'; import type { CopilotChatOptions } from '../providers';
import { toolError } from './error';
export const buildDocSearchGetter = ( export const buildDocSearchGetter = (
ac: AccessController, ac: AccessController,
@ -46,8 +47,8 @@ export const createDocSemanticSearchTool = (
execute: async ({ query }) => { execute: async ({ query }) => {
try { try {
return await searchDocs(query); return await searchDocs(query);
} catch { } catch (e: any) {
return 'Failed to search documents.'; return toolError('Doc Semantic Search Failed', e.message);
} }
}, },
}); });

View File

@ -0,0 +1,11 @@
export interface ToolError {
type: 'error';
name: string;
message: string;
}
export const toolError = (name: string, message: string): ToolError => ({
type: 'error',
name,
message,
});

View File

@ -0,0 +1,39 @@
import { tool } from 'ai';
import Exa from 'exa-js';
import { z } from 'zod';
import { Config } from '../../../base';
import { toolError } from './error';
export const createExaCrawlTool = (config: Config) => {
return tool({
description: 'Crawl the web url for information',
parameters: z.object({
url: z
.string()
.describe('The URL to crawl (including http:// or https://)'),
}),
execute: async ({ url }) => {
try {
const { key } = config.copilot.exa;
const exa = new Exa(key);
const result = await exa.getContents([url], {
livecrawl: 'always',
text: {
maxCharacters: 100000,
},
});
return result.results.map(data => ({
title: data.title,
url: data.url,
content: data.text,
favicon: data.favicon,
publishedDate: data.publishedDate,
author: data.author,
}));
} catch (e: any) {
return toolError('Exa Crawl Failed', e.message);
}
},
});
};

View File

@ -3,6 +3,7 @@ import Exa from 'exa-js';
import { z } from 'zod'; import { z } from 'zod';
import { Config } from '../../../base'; import { Config } from '../../../base';
import { toolError } from './error';
export const createExaSearchTool = (config: Config) => { export const createExaSearchTool = (config: Config) => {
return tool({ return tool({
@ -30,41 +31,8 @@ export const createExaSearchTool = (config: Config) => {
publishedDate: data.publishedDate, publishedDate: data.publishedDate,
author: data.author, author: data.author,
})); }));
} catch { } catch (e: any) {
return 'Failed to search the web'; return toolError('Exa Search Failed', e.message);
}
},
});
};
export const createExaCrawlTool = (config: Config) => {
return tool({
description: 'Crawl the web url for information',
parameters: z.object({
url: z
.string()
.describe('The URL to crawl (including http:// or https://)'),
}),
execute: async ({ url }) => {
try {
const { key } = config.copilot.exa;
const exa = new Exa(key);
const result = await exa.getContents([url], {
livecrawl: 'always',
text: {
maxCharacters: 100000,
},
});
return result.results.map(data => ({
title: data.title,
url: data.url,
content: data.text,
favicon: data.favicon,
publishedDate: data.publishedDate,
author: data.author,
}));
} catch {
return 'Failed to crawl the web url';
} }
}, },
}); });

View File

@ -1,3 +1,5 @@
export * from './doc-keyword-search'; export * from './doc-keyword-search';
export * from './doc-semantic-search'; export * from './doc-semantic-search';
export * from './web-search'; export * from './error';
export * from './exa-crawl';
export * from './exa-search';

View File

@ -0,0 +1,83 @@
#!/bin/bash
# Script to update code signing settings in iOS project for CI environment
# Updates project.pbxproj to use Apple Distribution certificate and team settings
set -e # Exit immediately if a command exits with a non-zero status
# Define target values
TARGET_IDENTITY="Apple Distribution: TOEVERYTHING PTE. LTD. (73YMMDVT2M)"
TARGET_SIGN_STYLE="Manual"
TARGET_TEAM="73YMMDVT2M"
TARGET_PROVISIONING_PROFILE="AppStore app.affine.pro"
# Get script directory and build absolute path to project file
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PBXPROJ_FILE="$SCRIPT_DIR/App/App.xcodeproj/project.pbxproj"
# Check if file exists
if [ ! -f "$PBXPROJ_FILE" ]; then
echo "❌ Error: project.pbxproj file not found: $PBXPROJ_FILE"
exit 1
fi
echo "🔍 Found project file: $PBXPROJ_FILE"
# Display current settings
echo "📋 Current code signing settings:"
echo "--- CODE_SIGN_IDENTITY ---"
grep -n "CODE_SIGN_IDENTITY" "$PBXPROJ_FILE" || echo "No CODE_SIGN_IDENTITY settings found"
echo "--- CODE_SIGN_STYLE ---"
grep -n "CODE_SIGN_STYLE" "$PBXPROJ_FILE" || echo "No CODE_SIGN_STYLE settings found"
echo "--- DEVELOPMENT_TEAM ---"
grep -n "DEVELOPMENT_TEAM" "$PBXPROJ_FILE" || echo "No DEVELOPMENT_TEAM settings found"
echo "--- PROVISIONING_PROFILE_SPECIFIER ---"
grep -n "PROVISIONING_PROFILE_SPECIFIER" "$PBXPROJ_FILE" || echo "No PROVISIONING_PROFILE_SPECIFIER settings found"
# Replace CODE_SIGN_IDENTITY settings
echo ""
echo "🔄 Replacing CODE_SIGN_IDENTITY..."
sed -i.tmp 's/CODE_SIGN_IDENTITY = "[^"]*";/CODE_SIGN_IDENTITY = "'"$TARGET_IDENTITY"'";/g' "$PBXPROJ_FILE"
# Replace CODE_SIGN_STYLE settings
echo "🔄 Replacing CODE_SIGN_STYLE..."
sed -i.tmp 's/CODE_SIGN_STYLE = [^;]*;/CODE_SIGN_STYLE = '"$TARGET_SIGN_STYLE"';/g' "$PBXPROJ_FILE"
# Replace DEVELOPMENT_TEAM settings
echo "🔄 Replacing DEVELOPMENT_TEAM..."
sed -i.tmp 's/DEVELOPMENT_TEAM = [^;]*;/DEVELOPMENT_TEAM = '"$TARGET_TEAM"';/g' "$PBXPROJ_FILE"
# Replace PROVISIONING_PROFILE_SPECIFIER settings
echo "🔄 Replacing PROVISIONING_PROFILE_SPECIFIER..."
sed -i.tmp 's/PROVISIONING_PROFILE_SPECIFIER = "[^"]*";/PROVISIONING_PROFILE_SPECIFIER = "'"$TARGET_PROVISIONING_PROFILE"'";/g' "$PBXPROJ_FILE"
# Remove temporary file
rm -f "${PBXPROJ_FILE}.tmp"
# Verify replacement results
echo ""
echo "✅ Replacement completed! New code signing settings:"
echo "--- CODE_SIGN_IDENTITY ---"
grep -n "CODE_SIGN_IDENTITY" "$PBXPROJ_FILE"
echo "--- CODE_SIGN_STYLE ---"
grep -n "CODE_SIGN_STYLE" "$PBXPROJ_FILE"
echo "--- DEVELOPMENT_TEAM ---"
grep -n "DEVELOPMENT_TEAM" "$PBXPROJ_FILE"
echo "--- PROVISIONING_PROFILE_SPECIFIER ---"
grep -n "PROVISIONING_PROFILE_SPECIFIER" "$PBXPROJ_FILE"
# Count replacements
IDENTITY_COUNT=$(grep -c "CODE_SIGN_IDENTITY.*$TARGET_IDENTITY" "$PBXPROJ_FILE")
STYLE_COUNT=$(grep -c "CODE_SIGN_STYLE.*$TARGET_SIGN_STYLE" "$PBXPROJ_FILE")
TEAM_COUNT=$(grep -c "DEVELOPMENT_TEAM.*$TARGET_TEAM" "$PBXPROJ_FILE")
PROVISIONING_COUNT=$(grep -c "PROVISIONING_PROFILE_SPECIFIER.*$TARGET_PROVISIONING_PROFILE" "$PBXPROJ_FILE")
echo ""
echo "📊 Replacement summary:"
echo " - CODE_SIGN_IDENTITY: $IDENTITY_COUNT replacements"
echo " - CODE_SIGN_STYLE: $STYLE_COUNT replacements"
echo " - DEVELOPMENT_TEAM: $TEAM_COUNT replacements"
echo " - PROVISIONING_PROFILE_SPECIFIER: $PROVISIONING_COUNT replacements"
echo ""
echo "🎉 Script execution completed successfully!"

View File

@ -20,8 +20,8 @@ import type {
SearchMenuConfig, SearchMenuConfig,
} from '../components/ai-chat-chips'; } from '../components/ai-chat-chips';
import type { import type {
AIModelSwitchConfig,
AINetworkSearchConfig, AINetworkSearchConfig,
AIPlaygroundConfig,
AIReasoningConfig, AIReasoningConfig,
} from '../components/ai-chat-input'; } from '../components/ai-chat-input';
import { import {
@ -224,7 +224,7 @@ export class ChatPanel extends SignalWatcher(
accessor reasoningConfig!: AIReasoningConfig; accessor reasoningConfig!: AIReasoningConfig;
@property({ attribute: false }) @property({ attribute: false })
accessor modelSwitchConfig!: AIModelSwitchConfig; accessor playgroundConfig!: AIPlaygroundConfig;
@property({ attribute: false }) @property({ attribute: false })
accessor appSidebarConfig!: AppSidebarConfig; accessor appSidebarConfig!: AppSidebarConfig;
@ -298,7 +298,7 @@ export class ChatPanel extends SignalWatcher(
.doc=${this.doc} .doc=${this.doc}
.networkSearchConfig=${this.networkSearchConfig} .networkSearchConfig=${this.networkSearchConfig}
.reasoningConfig=${this.reasoningConfig} .reasoningConfig=${this.reasoningConfig}
.modelSwitchConfig=${this.modelSwitchConfig} .playgroundConfig=${this.playgroundConfig}
.appSidebarConfig=${this.appSidebarConfig} .appSidebarConfig=${this.appSidebarConfig}
.searchMenuConfig=${this.searchMenuConfig} .searchMenuConfig=${this.searchMenuConfig}
.docDisplayConfig=${this.docDisplayConfig} .docDisplayConfig=${this.docDisplayConfig}
@ -437,7 +437,7 @@ export class ChatPanel extends SignalWatcher(
>` >`
: 'AFFiNE AI'} : 'AFFiNE AI'}
</div> </div>
${this.modelSwitchConfig.visible.value ${this.playgroundConfig.visible.value
? html` ? html`
<div class="chat-panel-playground" @click=${this._openPlayground}> <div class="chat-panel-playground" @click=${this._openPlayground}>
${CenterPeekIcon()} ${CenterPeekIcon()}
@ -478,7 +478,7 @@ export class ChatPanel extends SignalWatcher(
.isVisible=${this._isSidebarOpen} .isVisible=${this._isSidebarOpen}
.networkSearchConfig=${this.networkSearchConfig} .networkSearchConfig=${this.networkSearchConfig}
.reasoningConfig=${this.reasoningConfig} .reasoningConfig=${this.reasoningConfig}
.modelSwitchConfig=${this.modelSwitchConfig} .playgroundConfig=${this.playgroundConfig}
.docDisplayConfig=${this.docDisplayConfig} .docDisplayConfig=${this.docDisplayConfig}
.searchMenuConfig=${this.searchMenuConfig} .searchMenuConfig=${this.searchMenuConfig}
.trackOptions=${{ .trackOptions=${{

View File

@ -29,7 +29,6 @@ import type {
import { isCollectionChip, isDocChip, isTagChip } from '../ai-chat-chips'; import { isCollectionChip, isDocChip, isTagChip } from '../ai-chat-chips';
import type { import type {
AIChatInputContext, AIChatInputContext,
AIModelSwitchConfig,
AINetworkSearchConfig, AINetworkSearchConfig,
AIReasoningConfig, AIReasoningConfig,
} from '../ai-chat-input'; } from '../ai-chat-input';
@ -93,9 +92,6 @@ export class AIChatComposer extends SignalWatcher(
@property({ attribute: false }) @property({ attribute: false })
accessor searchMenuConfig!: SearchMenuConfig; accessor searchMenuConfig!: SearchMenuConfig;
@property({ attribute: false })
accessor modelSwitchConfig!: AIModelSwitchConfig;
@property({ attribute: false }) @property({ attribute: false })
accessor onChatSuccess: (() => void) | undefined; accessor onChatSuccess: (() => void) | undefined;
@ -151,7 +147,6 @@ export class AIChatComposer extends SignalWatcher(
.updateContext=${this.updateContext} .updateContext=${this.updateContext}
.networkSearchConfig=${this.networkSearchConfig} .networkSearchConfig=${this.networkSearchConfig}
.reasoningConfig=${this.reasoningConfig} .reasoningConfig=${this.reasoningConfig}
.modelSwitchConfig=${this.modelSwitchConfig}
.docDisplayConfig=${this.docDisplayConfig} .docDisplayConfig=${this.docDisplayConfig}
.onChatSuccess=${this.onChatSuccess} .onChatSuccess=${this.onChatSuccess}
.trackOptions=${this.trackOptions} .trackOptions=${this.trackOptions}

View File

@ -27,7 +27,6 @@ import {
import { MAX_IMAGE_COUNT } from './const'; import { MAX_IMAGE_COUNT } from './const';
import type { import type {
AIChatInputContext, AIChatInputContext,
AIModelSwitchConfig,
AINetworkSearchConfig, AINetworkSearchConfig,
AIReasoningConfig, AIReasoningConfig,
} from './type'; } from './type';
@ -320,9 +319,6 @@ export class AIChatInput extends SignalWatcher(
@property({ attribute: false }) @property({ attribute: false })
accessor reasoningConfig!: AIReasoningConfig; accessor reasoningConfig!: AIReasoningConfig;
@property({ attribute: false })
accessor modelSwitchConfig: AIModelSwitchConfig | undefined = undefined;
@property({ attribute: false }) @property({ attribute: false })
accessor docDisplayConfig!: DocDisplayConfig; accessor docDisplayConfig!: DocDisplayConfig;
@ -442,7 +438,6 @@ export class AIChatInput extends SignalWatcher(
</div> </div>
<div class="chat-input-footer-spacer"></div> <div class="chat-input-footer-spacer"></div>
<chat-input-preference <chat-input-preference
.modelSwitchConfig=${this.modelSwitchConfig}
.session=${this.session} .session=${this.session}
.onModelChange=${this._handleModelChange} .onModelChange=${this._handleModelChange}
.modelId=${this.modelId} .modelId=${this.modelId}

View File

@ -15,8 +15,6 @@ import { ShadowlessElement } from '@blocksuite/std';
import { css, html } from 'lit'; import { css, html } from 'lit';
import { property } from 'lit/decorators.js'; import { property } from 'lit/decorators.js';
import type { AIModelSwitchConfig } from './type';
export class ChatInputPreference extends SignalWatcher( export class ChatInputPreference extends SignalWatcher(
WithDisposable(ShadowlessElement) WithDisposable(ShadowlessElement)
) { ) {
@ -51,10 +49,6 @@ export class ChatInputPreference extends SignalWatcher(
@property({ attribute: false }) @property({ attribute: false })
accessor session!: CopilotSessionType | undefined; accessor session!: CopilotSessionType | undefined;
// --------- model props start ---------
@property({ attribute: false })
accessor modelSwitchConfig: AIModelSwitchConfig | undefined = undefined;
@property({ attribute: false }) @property({ attribute: false })
accessor onModelChange: ((modelId: string) => void) | undefined; accessor onModelChange: ((modelId: string) => void) | undefined;
@ -96,7 +90,6 @@ export class ChatInputPreference extends SignalWatcher(
const searchItems = []; const searchItems = [];
// model switch // model switch
if (this.modelSwitchConfig?.visible.value) {
modelItems.push( modelItems.push(
menu.subMenu({ menu.subMenu({
name: 'Model', name: 'Model',
@ -111,7 +104,6 @@ export class ChatInputPreference extends SignalWatcher(
}, },
}) })
); );
}
modelItems.push( modelItems.push(
menu.toggleSwitch({ menu.toggleSwitch({

View File

@ -14,7 +14,7 @@ export interface AIReasoningConfig {
setEnabled: (state: boolean) => void; setEnabled: (state: boolean) => void;
} }
export interface AIModelSwitchConfig { export interface AIPlaygroundConfig {
visible: Signal<boolean | undefined>; visible: Signal<boolean | undefined>;
} }

View File

@ -1,6 +1,5 @@
import type { FeatureFlagService } from '@affine/core/modules/feature-flag'; import type { FeatureFlagService } from '@affine/core/modules/feature-flag';
import { WithDisposable } from '@blocksuite/affine/global/lit'; import { WithDisposable } from '@blocksuite/affine/global/lit';
import { unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
import type { EditorHost } from '@blocksuite/affine/std'; import type { EditorHost } from '@blocksuite/affine/std';
import { ShadowlessElement } from '@blocksuite/affine/std'; import { ShadowlessElement } from '@blocksuite/affine/std';
import type { ExtensionType } from '@blocksuite/affine/store'; import type { ExtensionType } from '@blocksuite/affine/store';
@ -21,13 +20,6 @@ export class ChatContentStreamObjects extends WithDisposable(
border-radius: 8px; border-radius: 8px;
background-color: rgba(0, 0, 0, 0.05); background-color: rgba(0, 0, 0, 0.05);
} }
.tool-wrapper {
padding: 12px;
margin: 8px 0;
border-radius: 8px;
border: 0.5px solid ${unsafeCSSVarV2('layer/insideBorder/border')};
}
`; `;
@property({ attribute: false }) @property({ attribute: false })
@ -70,14 +62,18 @@ export class ChatContentStreamObjects extends WithDisposable(
.width=${this.width} .width=${this.width}
></web-search-tool> ></web-search-tool>
`; `;
default: default: {
const name = streamObject.toolName + ' tool calling';
return html` return html`
<div class="tool-wrapper"> <tool-call-card
${streamObject.toolName} tool calling... .name=${name}
</div> .host=${this.host}
.width=${this.width}
></tool-call-card>
`; `;
} }
} }
}
private renderToolResult(streamObject: StreamObject) { private renderToolResult(streamObject: StreamObject) {
if (streamObject.type !== 'tool-result') { if (streamObject.type !== 'tool-result') {
@ -101,14 +97,18 @@ export class ChatContentStreamObjects extends WithDisposable(
.width=${this.width} .width=${this.width}
></web-search-tool> ></web-search-tool>
`; `;
default: default: {
const name = streamObject.toolName + ' tool result';
return html` return html`
<div class="tool-wrapper"> <tool-result-card
${streamObject.toolName} tool result... .name=${name}
</div> .host=${this.host}
.width=${this.width}
></tool-result-card>
`; `;
} }
} }
}
private renderRichText(text: string) { private renderRichText(text: string) {
return html`<chat-content-rich-text return html`<chat-content-rich-text

View File

@ -1,6 +1,7 @@
import { WithDisposable } from '@blocksuite/affine/global/lit'; import { WithDisposable } from '@blocksuite/affine/global/lit';
import { unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme'; import { unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
import { ShadowlessElement } from '@blocksuite/affine/std'; import { ShadowlessElement } from '@blocksuite/affine/std';
import { ToolIcon } from '@blocksuite/icons/lit';
import { css, html, type TemplateResult } from 'lit'; import { css, html, type TemplateResult } from 'lit';
import { property, state } from 'lit/decorators.js'; import { property, state } from 'lit/decorators.js';
@ -51,10 +52,10 @@ export class ToolCallCard extends WithDisposable(ShadowlessElement) {
`; `;
@property({ attribute: false }) @property({ attribute: false })
accessor name!: string; accessor name: string = 'Tool calling';
@property({ attribute: false }) @property({ attribute: false })
accessor icon!: TemplateResult<1>; accessor icon: TemplateResult<1> = ToolIcon();
@state() @state()
private accessor dotsText = '.'; private accessor dotsText = '.';

View File

@ -0,0 +1,64 @@
import { WithDisposable } from '@blocksuite/affine/global/lit';
import { unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
import { ShadowlessElement } from '@blocksuite/affine/std';
import { ToolIcon } from '@blocksuite/icons/lit';
import { css, html, type TemplateResult } from 'lit';
import { property } from 'lit/decorators.js';
export class ToolFailedCard extends WithDisposable(ShadowlessElement) {
static override styles = css`
.ai-tool-failed-wrapper {
padding: 12px;
margin: 8px 0;
border-radius: 8px;
border: 0.5px solid ${unsafeCSSVarV2('layer/insideBorder/border')};
.ai-tool-header {
display: flex;
justify-content: space-between;
align-items: center;
gap: 8px;
}
.ai-icon {
width: 24px;
height: 24px;
svg {
width: 24px;
height: 24px;
color: ${unsafeCSSVarV2('button/error')};
}
}
.ai-error-name {
font-size: 14px;
font-weight: 500;
line-height: 24px;
margin-left: 0px;
margin-right: auto;
color: ${unsafeCSSVarV2('button/error')};
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
`;
@property({ attribute: false })
accessor name: string = 'Tool calling failed';
@property({ attribute: false })
accessor icon: TemplateResult<1> = ToolIcon();
protected override render() {
return html`
<div class="ai-tool-failed-wrapper">
<div class="ai-tool-header">
<div class="ai-icon">${this.icon}</div>
<div class="ai-error-name">${this.name}</div>
</div>
</div>
`;
}
}

View File

@ -2,7 +2,7 @@ import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit';
import { ImageProxyService } from '@blocksuite/affine/shared/adapters'; import { ImageProxyService } from '@blocksuite/affine/shared/adapters';
import { unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme'; import { unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
import { type EditorHost, ShadowlessElement } from '@blocksuite/affine/std'; import { type EditorHost, ShadowlessElement } from '@blocksuite/affine/std';
import { ToggleDownIcon } from '@blocksuite/icons/lit'; import { ToggleDownIcon, ToolIcon } from '@blocksuite/icons/lit';
import { type Signal } from '@preact/signals-core'; import { type Signal } from '@preact/signals-core';
import { css, html, nothing, type TemplateResult } from 'lit'; import { css, html, nothing, type TemplateResult } from 'lit';
import { property, state } from 'lit/decorators.js'; import { property, state } from 'lit/decorators.js';
@ -17,7 +17,7 @@ export class ToolResultCard extends SignalWatcher(
WithDisposable(ShadowlessElement) WithDisposable(ShadowlessElement)
) { ) {
static override styles = css` static override styles = css`
.ai-tool-wrapper { .ai-tool-result-wrapper {
padding: 12px; padding: 12px;
margin: 8px 0; margin: 8px 0;
border-radius: 8px; border-radius: 8px;
@ -158,16 +158,16 @@ export class ToolResultCard extends SignalWatcher(
accessor host!: EditorHost; accessor host!: EditorHost;
@property({ attribute: false }) @property({ attribute: false })
accessor name!: string; accessor name: string = 'Tool result';
@property({ attribute: false }) @property({ attribute: false })
accessor icon!: TemplateResult<1> | string; accessor icon: TemplateResult<1> | string = ToolIcon();
@property({ attribute: false }) @property({ attribute: false })
accessor footerIcons: TemplateResult<1>[] | string[] = []; accessor footerIcons: TemplateResult<1>[] | string[] = [];
@property({ attribute: false }) @property({ attribute: false })
accessor results!: ToolResult[]; accessor results: ToolResult[] = [];
@property({ attribute: false }) @property({ attribute: false })
accessor width: Signal<number | undefined> | undefined; accessor width: Signal<number | undefined> | undefined;
@ -177,7 +177,7 @@ export class ToolResultCard extends SignalWatcher(
protected override render() { protected override render() {
return html` return html`
<div class="ai-tool-wrapper"> <div class="ai-tool-result-wrapper">
<div class="ai-tool-header" @click=${this.toggleCard}> <div class="ai-tool-header" @click=${this.toggleCard}>
<div class="ai-icon">${this.renderIcon(this.icon)}</div> <div class="ai-icon">${this.renderIcon(this.icon)}</div>
<div class="ai-tool-name">${this.name}</div> <div class="ai-tool-name">${this.name}</div>

View File

@ -0,0 +1,5 @@
export interface ToolError {
type: 'error';
name: string;
message: string;
}

View File

@ -5,6 +5,8 @@ import type { Signal } from '@preact/signals-core';
import { html, nothing } from 'lit'; import { html, nothing } from 'lit';
import { property } from 'lit/decorators.js'; import { property } from 'lit/decorators.js';
import type { ToolError } from './type';
interface WebCrawlToolCall { interface WebCrawlToolCall {
type: 'tool-call'; type: 'tool-call';
toolCallId: string; toolCallId: string;
@ -17,14 +19,17 @@ interface WebCrawlToolResult {
toolCallId: string; toolCallId: string;
toolName: string; toolName: string;
args: { url: string }; args: { url: string };
result: Array<{ result:
| Array<{
title: string; title: string;
url: string; url: string;
content: string; content: string;
favicon: string; favicon: string;
publishedDate: string; publishedDate: string;
author: string; author: string;
}>; }>
| ToolError
| null;
} }
export class WebCrawlTool extends WithDisposable(ShadowlessElement) { export class WebCrawlTool extends WithDisposable(ShadowlessElement) {
@ -51,8 +56,9 @@ export class WebCrawlTool extends WithDisposable(ShadowlessElement) {
return nothing; return nothing;
} }
const { favicon, title, content } = this.data.result[0]; const result = this.data.result;
if (result && Array.isArray(result)) {
const { favicon, title, content } = result[0];
return html` return html`
<tool-result-card <tool-result-card
.host=${this.host} .host=${this.host}
@ -71,6 +77,14 @@ export class WebCrawlTool extends WithDisposable(ShadowlessElement) {
`; `;
} }
return html`
<tool-call-failed
.name=${'Web reading failed'}
.icon=${WebIcon()}
></tool-call-failed>
`;
}
protected override render() { protected override render() {
const { data } = this; const { data } = this;

View File

@ -5,6 +5,8 @@ import type { Signal } from '@preact/signals-core';
import { html, nothing } from 'lit'; import { html, nothing } from 'lit';
import { property } from 'lit/decorators.js'; import { property } from 'lit/decorators.js';
import type { ToolError } from './type';
interface WebSearchToolCall { interface WebSearchToolCall {
type: 'tool-call'; type: 'tool-call';
toolCallId: string; toolCallId: string;
@ -17,14 +19,17 @@ interface WebSearchToolResult {
toolCallId: string; toolCallId: string;
toolName: string; toolName: string;
args: { url: string }; args: { url: string };
result: Array<{ result:
| Array<{
title: string; title: string;
url: string; url: string;
content: string; content: string;
favicon: string; favicon: string;
publishedDate: string; publishedDate: string;
author: string; author: string;
}>; }>
| ToolError
| null;
} }
export class WebSearchTool extends WithDisposable(ShadowlessElement) { export class WebSearchTool extends WithDisposable(ShadowlessElement) {
@ -50,7 +55,9 @@ export class WebSearchTool extends WithDisposable(ShadowlessElement) {
return nothing; return nothing;
} }
const results = this.data.result.map(item => { const result = this.data.result;
if (result && Array.isArray(result)) {
const results = result.map(item => {
const { favicon, title, content } = item; const { favicon, title, content } = item;
return { return {
title: title, title: title,
@ -58,9 +65,7 @@ export class WebSearchTool extends WithDisposable(ShadowlessElement) {
content: content, content: content,
}; };
}); });
const footerIcons = this.data.result const footerIcons = result.map(item => item.favicon).filter(Boolean);
.map(item => item.favicon)
.filter(Boolean);
return html` return html`
<tool-result-card <tool-result-card
@ -74,6 +79,14 @@ export class WebSearchTool extends WithDisposable(ShadowlessElement) {
`; `;
} }
return html`
<tool-call-failed
.name=${'Web search failed'}
.icon=${WebIcon()}
></tool-call-failed>
`;
}
protected override render() { protected override render() {
const { data } = this; const { data } = this;

View File

@ -18,8 +18,8 @@ import type { ChatPanelMessages } from '../../chat-panel/chat-panel-messages';
import { AIProvider } from '../../provider'; import { AIProvider } from '../../provider';
import type { DocDisplayConfig, SearchMenuConfig } from '../ai-chat-chips'; import type { DocDisplayConfig, SearchMenuConfig } from '../ai-chat-chips';
import type { import type {
AIModelSwitchConfig,
AINetworkSearchConfig, AINetworkSearchConfig,
AIPlaygroundConfig,
AIReasoningConfig, AIReasoningConfig,
} from '../ai-chat-input'; } from '../ai-chat-input';
import { import {
@ -136,7 +136,7 @@ export class PlaygroundChat extends SignalWatcher(
accessor reasoningConfig!: AIReasoningConfig; accessor reasoningConfig!: AIReasoningConfig;
@property({ attribute: false }) @property({ attribute: false })
accessor modelSwitchConfig!: AIModelSwitchConfig; accessor playgroundConfig!: AIPlaygroundConfig;
@property({ attribute: false }) @property({ attribute: false })
accessor appSidebarConfig!: AppSidebarConfig; accessor appSidebarConfig!: AppSidebarConfig;
@ -320,7 +320,7 @@ export class PlaygroundChat extends SignalWatcher(
.isVisible=${this._isVisible} .isVisible=${this._isVisible}
.networkSearchConfig=${this.networkSearchConfig} .networkSearchConfig=${this.networkSearchConfig}
.reasoningConfig=${this.reasoningConfig} .reasoningConfig=${this.reasoningConfig}
.modelSwitchConfig=${this.modelSwitchConfig} .playgroundConfig=${this.playgroundConfig}
.docDisplayConfig=${this.docDisplayConfig} .docDisplayConfig=${this.docDisplayConfig}
.searchMenuConfig=${this.searchMenuConfig} .searchMenuConfig=${this.searchMenuConfig}
></ai-chat-composer> ></ai-chat-composer>

View File

@ -12,8 +12,8 @@ import type { AppSidebarConfig } from '../../chat-panel/chat-config';
import { AIProvider } from '../../provider'; import { AIProvider } from '../../provider';
import type { DocDisplayConfig, SearchMenuConfig } from '../ai-chat-chips'; import type { DocDisplayConfig, SearchMenuConfig } from '../ai-chat-chips';
import type { import type {
AIModelSwitchConfig,
AINetworkSearchConfig, AINetworkSearchConfig,
AIPlaygroundConfig,
AIReasoningConfig, AIReasoningConfig,
} from '../ai-chat-input'; } from '../ai-chat-input';
@ -66,7 +66,7 @@ export class PlaygroundContent extends SignalWatcher(
accessor reasoningConfig!: AIReasoningConfig; accessor reasoningConfig!: AIReasoningConfig;
@property({ attribute: false }) @property({ attribute: false })
accessor modelSwitchConfig!: AIModelSwitchConfig; accessor playgroundConfig!: AIPlaygroundConfig;
@property({ attribute: false }) @property({ attribute: false })
accessor appSidebarConfig!: AppSidebarConfig; accessor appSidebarConfig!: AppSidebarConfig;
@ -329,7 +329,7 @@ export class PlaygroundContent extends SignalWatcher(
.doc=${this.doc} .doc=${this.doc}
.networkSearchConfig=${this.networkSearchConfig} .networkSearchConfig=${this.networkSearchConfig}
.reasoningConfig=${this.reasoningConfig} .reasoningConfig=${this.reasoningConfig}
.modelSwitchConfig=${this.modelSwitchConfig} .playgroundConfig=${this.playgroundConfig}
.appSidebarConfig=${this.appSidebarConfig} .appSidebarConfig=${this.appSidebarConfig}
.searchMenuConfig=${this.searchMenuConfig} .searchMenuConfig=${this.searchMenuConfig}
.docDisplayConfig=${this.docDisplayConfig} .docDisplayConfig=${this.docDisplayConfig}

View File

@ -45,6 +45,7 @@ import { ChatContentRichText } from './components/ai-message-content/rich-text';
import { ChatContentStreamObjects } from './components/ai-message-content/stream-objects'; import { ChatContentStreamObjects } from './components/ai-message-content/stream-objects';
import { AIScrollableTextRenderer } from './components/ai-scrollable-text-renderer'; import { AIScrollableTextRenderer } from './components/ai-scrollable-text-renderer';
import { ToolCallCard } from './components/ai-tools/tool-call-card'; import { ToolCallCard } from './components/ai-tools/tool-call-card';
import { ToolFailedCard } from './components/ai-tools/tool-failed-card';
import { ToolResultCard } from './components/ai-tools/tool-result-card'; import { ToolResultCard } from './components/ai-tools/tool-result-card';
import { WebCrawlTool } from './components/ai-tools/web-crawl'; import { WebCrawlTool } from './components/ai-tools/web-crawl';
import { WebSearchTool } from './components/ai-tools/web-search'; import { WebSearchTool } from './components/ai-tools/web-search';
@ -167,6 +168,7 @@ export function registerAIEffects() {
customElements.define('tool-call-card', ToolCallCard); customElements.define('tool-call-card', ToolCallCard);
customElements.define('tool-result-card', ToolResultCard); customElements.define('tool-result-card', ToolResultCard);
customElements.define('tool-call-failed', ToolFailedCard);
customElements.define('web-crawl-tool', WebCrawlTool); customElements.define('web-crawl-tool', WebCrawlTool);
customElements.define('web-search-tool', WebSearchTool); customElements.define('web-search-tool', WebSearchTool);

View File

@ -1,6 +1,6 @@
// packages/frontend/core/src/blocksuite/ai/hooks/useChatPanelConfig.ts // packages/frontend/core/src/blocksuite/ai/hooks/useChatPanelConfig.ts
import { AIModelSwitchService } from '@affine/core/modules/ai-button/services/model-switch';
import { AINetworkSearchService } from '@affine/core/modules/ai-button/services/network-search'; import { AINetworkSearchService } from '@affine/core/modules/ai-button/services/network-search';
import { AIPlaygroundService } from '@affine/core/modules/ai-button/services/playground';
import { AIReasoningService } from '@affine/core/modules/ai-button/services/reasoning'; import { AIReasoningService } from '@affine/core/modules/ai-button/services/reasoning';
import { CollectionService } from '@affine/core/modules/collection'; import { CollectionService } from '@affine/core/modules/collection';
import { DocsService } from '@affine/core/modules/doc'; import { DocsService } from '@affine/core/modules/doc';
@ -22,7 +22,7 @@ export function useAIChatConfig() {
const searchService = framework.get(AINetworkSearchService); const searchService = framework.get(AINetworkSearchService);
const reasoningService = framework.get(AIReasoningService); const reasoningService = framework.get(AIReasoningService);
const modelSwitchService = framework.get(AIModelSwitchService); const playgroundService = framework.get(AIPlaygroundService);
const docDisplayMetaService = framework.get(DocDisplayMetaService); const docDisplayMetaService = framework.get(DocDisplayMetaService);
const workspaceService = framework.get(WorkspaceService); const workspaceService = framework.get(WorkspaceService);
const searchMenuService = framework.get(SearchMenuService); const searchMenuService = framework.get(SearchMenuService);
@ -42,8 +42,8 @@ export function useAIChatConfig() {
setEnabled: reasoningService.setEnabled, setEnabled: reasoningService.setEnabled,
}; };
const modelSwitchConfig = { const playgroundConfig = {
visible: modelSwitchService.visible, visible: playgroundService.visible,
}; };
const docDisplayConfig = { const docDisplayConfig = {
@ -130,6 +130,6 @@ export function useAIChatConfig() {
reasoningConfig, reasoningConfig,
docDisplayConfig, docDisplayConfig,
searchMenuConfig, searchMenuConfig,
modelSwitchConfig, playgroundConfig,
}; };
} }

View File

@ -47,7 +47,7 @@ export const EditorChatPanel = forwardRef(function EditorChatPanel(
searchMenuConfig, searchMenuConfig,
networkSearchConfig, networkSearchConfig,
reasoningConfig, reasoningConfig,
modelSwitchConfig, playgroundConfig,
} = useAIChatConfig(); } = useAIChatConfig();
useEffect(() => { useEffect(() => {
@ -74,7 +74,7 @@ export const EditorChatPanel = forwardRef(function EditorChatPanel(
chatPanelRef.current.searchMenuConfig = searchMenuConfig; chatPanelRef.current.searchMenuConfig = searchMenuConfig;
chatPanelRef.current.networkSearchConfig = networkSearchConfig; chatPanelRef.current.networkSearchConfig = networkSearchConfig;
chatPanelRef.current.reasoningConfig = reasoningConfig; chatPanelRef.current.reasoningConfig = reasoningConfig;
chatPanelRef.current.modelSwitchConfig = modelSwitchConfig; chatPanelRef.current.playgroundConfig = playgroundConfig;
chatPanelRef.current.extensions = editor.host.std chatPanelRef.current.extensions = editor.host.std
.get(ViewExtensionManagerIdentifier) .get(ViewExtensionManagerIdentifier)
.get('preview-page'); .get('preview-page');
@ -109,7 +109,7 @@ export const EditorChatPanel = forwardRef(function EditorChatPanel(
networkSearchConfig, networkSearchConfig,
searchMenuConfig, searchMenuConfig,
reasoningConfig, reasoningConfig,
modelSwitchConfig, playgroundConfig,
]); ]);
return <div className={styles.root} ref={containerRef} />; return <div className={styles.root} ref={containerRef} />;

View File

@ -7,8 +7,8 @@ import { FeatureFlagService } from '../feature-flag';
import { GlobalStateService } from '../storage'; import { GlobalStateService } from '../storage';
import { AIButtonProvider } from './provider/ai-button'; import { AIButtonProvider } from './provider/ai-button';
import { AIButtonService } from './services/ai-button'; import { AIButtonService } from './services/ai-button';
import { AIModelSwitchService } from './services/model-switch';
import { AINetworkSearchService } from './services/network-search'; import { AINetworkSearchService } from './services/network-search';
import { AIPlaygroundService } from './services/playground';
import { AIReasoningService } from './services/reasoning'; import { AIReasoningService } from './services/reasoning';
export const configureAIButtonModule = (framework: Framework) => { export const configureAIButtonModule = (framework: Framework) => {
@ -28,6 +28,6 @@ export function configureAIReasoningModule(framework: Framework) {
framework.service(AIReasoningService, [GlobalStateService]); framework.service(AIReasoningService, [GlobalStateService]);
} }
export function configureAIModelSwitchModule(framework: Framework) { export function configureAIPlaygroundModule(framework: Framework) {
framework.service(AIModelSwitchService, [FeatureFlagService]); framework.service(AIPlaygroundService, [FeatureFlagService]);
} }

View File

@ -6,7 +6,7 @@ import { Service } from '@toeverything/infra';
import type { FeatureFlagService } from '../../feature-flag'; import type { FeatureFlagService } from '../../feature-flag';
export class AIModelSwitchService extends Service { export class AIPlaygroundService extends Service {
constructor(private readonly featureFlagService: FeatureFlagService) { constructor(private readonly featureFlagService: FeatureFlagService) {
super(); super();
@ -22,5 +22,5 @@ export class AIModelSwitchService extends Service {
visible: Signal<boolean | undefined>; visible: Signal<boolean | undefined>;
private readonly _visible$ = private readonly _visible$ =
this.featureFlagService.flags.enable_ai_model_switch.$; this.featureFlagService.flags.enable_ai_playground.$;
} }

View File

@ -26,7 +26,7 @@ export const AFFINE_FLAGS = {
configurable: false, configurable: false,
defaultState: true, defaultState: true,
}, },
enable_ai_model_switch: { enable_ai_playground: {
category: 'affine', category: 'affine',
displayName: displayName:
'com.affine.settings.workspace.experimental-features.enable-ai-model-switch.name', 'com.affine.settings.workspace.experimental-features.enable-ai-model-switch.name',

View File

@ -3,8 +3,8 @@ import { type Framework } from '@toeverything/infra';
import { import {
configureAIButtonModule, configureAIButtonModule,
configureAIModelSwitchModule,
configureAINetworkSearchModule, configureAINetworkSearchModule,
configureAIPlaygroundModule,
configureAIReasoningModule, configureAIReasoningModule,
} from './ai-button'; } from './ai-button';
import { configureAppSidebarModule } from './app-sidebar'; import { configureAppSidebarModule } from './app-sidebar';
@ -107,7 +107,7 @@ export function configureCommonModules(framework: Framework) {
configureCommonGlobalStorageImpls(framework); configureCommonGlobalStorageImpls(framework);
configureAINetworkSearchModule(framework); configureAINetworkSearchModule(framework);
configureAIReasoningModule(framework); configureAIReasoningModule(framework);
configureAIModelSwitchModule(framework); configureAIPlaygroundModule(framework);
configureAIButtonModule(framework); configureAIButtonModule(framework);
configureTemplateDocModule(framework); configureTemplateDocModule(framework);
configureBlobManagementModule(framework); configureBlobManagementModule(framework);

View File

@ -5692,6 +5692,14 @@ export function useAFFiNEI18N(): {
* `Enable or disable AI model switch feature.` * `Enable or disable AI model switch feature.`
*/ */
["com.affine.settings.workspace.experimental-features.enable-ai-model-switch.description"](): string; ["com.affine.settings.workspace.experimental-features.enable-ai-model-switch.description"](): string;
/**
* `Enable AI Playground`
*/
["com.affine.settings.workspace.experimental-features.enable-ai-playground.name"](): string;
/**
* `Enable or disable AI playground feature.`
*/
["com.affine.settings.workspace.experimental-features.enable-ai-playground.description"](): string;
/** /**
* `Database Full Width` * `Database Full Width`
*/ */

View File

@ -1423,6 +1423,8 @@
"com.affine.settings.workspace.experimental-features.enable-ai-network-search.description": "Enable or disable AI Network Search feature.", "com.affine.settings.workspace.experimental-features.enable-ai-network-search.description": "Enable or disable AI Network Search feature.",
"com.affine.settings.workspace.experimental-features.enable-ai-model-switch.name": "Enable AI Model Switch", "com.affine.settings.workspace.experimental-features.enable-ai-model-switch.name": "Enable AI Model Switch",
"com.affine.settings.workspace.experimental-features.enable-ai-model-switch.description": "Enable or disable AI model switch feature.", "com.affine.settings.workspace.experimental-features.enable-ai-model-switch.description": "Enable or disable AI model switch feature.",
"com.affine.settings.workspace.experimental-features.enable-ai-playground.name": "Enable AI Playground",
"com.affine.settings.workspace.experimental-features.enable-ai-playground.description": "Enable or disable AI playground feature.",
"com.affine.settings.workspace.experimental-features.enable-database-full-width.name": "Database Full Width", "com.affine.settings.workspace.experimental-features.enable-database-full-width.name": "Database Full Width",
"com.affine.settings.workspace.experimental-features.enable-database-full-width.description": "The database will be displayed in full-width mode.", "com.affine.settings.workspace.experimental-features.enable-database-full-width.description": "The database will be displayed in full-width mode.",
"com.affine.settings.workspace.experimental-features.enable-database-attachment-note.name": "Database Attachment Note", "com.affine.settings.workspace.experimental-features.enable-database-attachment-note.name": "Database Attachment Note",

View File

@ -71,6 +71,32 @@ update_app_stream_version() {
rm "$file_path".bak rm "$file_path".bak
} }
update_ios_marketing_version() {
local file_path=$1
local new_version=$2
# Check if file exists
if [ ! -f "$file_path" ]; then
echo "Error: File does not exist at $file_path."
return 1
fi
echo "Updating $file_path with MARKETING_VERSION $new_version"
# Use sed to replace the MARKETING_VERSION value with the new version
sed -i.bak -E "s/MARKETING_VERSION = [^;]*;/MARKETING_VERSION = $new_version;/g" "$file_path"
# Check if sed command succeeded
if [ $? -ne 0 ]; then
echo "Error: Failed to update the MARKETING_VERSION."
return 1
fi
echo "MARKETING_VERSION in $file_path updated to $new_version"
rm "$file_path".bak
}
new_version=$1 new_version=$1
update_app_version_in_helm_charts ".github/helm/affine/Chart.yaml" "$new_version" update_app_version_in_helm_charts ".github/helm/affine/Chart.yaml" "$new_version"
@ -80,3 +106,5 @@ update_app_version_in_helm_charts ".github/helm/affine/charts/renderer/Chart.yam
update_app_version_in_helm_charts ".github/helm/affine/charts/doc/Chart.yaml" "$new_version" update_app_version_in_helm_charts ".github/helm/affine/charts/doc/Chart.yaml" "$new_version"
update_app_stream_version "packages/frontend/apps/electron/resources/affine.metainfo.xml" "$new_version" update_app_stream_version "packages/frontend/apps/electron/resources/affine.metainfo.xml" "$new_version"
update_ios_marketing_version "packages/frontend/apps/ios/App/App.xcodeproj/project.pbxproj" "$new_version"