mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-04 08:38:34 +00:00
Compare commits
11 Commits
v0.23.0-ca
...
v0.21.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb606ac3e5 | ||
|
|
851111e1e4 | ||
|
|
9982e0ea45 | ||
|
|
58f7a6166c | ||
|
|
07d7a62071 | ||
|
|
ab3f056927 | ||
|
|
61e3364717 | ||
|
|
e32d6b9347 | ||
|
|
075a2e9f99 | ||
|
|
8b486b4833 | ||
|
|
21b7f02b0f |
@@ -249,6 +249,7 @@ export class LinkedDocPopover extends SignalWatcher(
|
||||
override disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this._menusItemsEffectCleanup();
|
||||
this._updateLinkedDocGroupAbortController?.abort();
|
||||
}
|
||||
|
||||
override render() {
|
||||
|
||||
@@ -385,6 +385,45 @@ test('should create message correctly', async t => {
|
||||
t.truthy(messageId, 'should be able to create message with valid session');
|
||||
}
|
||||
|
||||
{
|
||||
// with attachment url
|
||||
{
|
||||
const { id } = await createWorkspace(app);
|
||||
const sessionId = await createCopilotSession(
|
||||
app,
|
||||
id,
|
||||
randomUUID(),
|
||||
promptName
|
||||
);
|
||||
const messageId = await createCopilotMessage(app, sessionId, undefined, [
|
||||
'http://example.com/cat.jpg',
|
||||
]);
|
||||
t.truthy(messageId, 'should be able to create message with url link');
|
||||
}
|
||||
|
||||
// with attachment
|
||||
{
|
||||
const { id } = await createWorkspace(app);
|
||||
const sessionId = await createCopilotSession(
|
||||
app,
|
||||
id,
|
||||
randomUUID(),
|
||||
promptName
|
||||
);
|
||||
const smallestPng =
|
||||
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII';
|
||||
const pngData = await fetch(smallestPng).then(res => res.arrayBuffer());
|
||||
const messageId = await createCopilotMessage(
|
||||
app,
|
||||
sessionId,
|
||||
undefined,
|
||||
undefined,
|
||||
[new File([new Uint8Array(pngData)], '1.png', { type: 'image/png' })]
|
||||
);
|
||||
t.truthy(messageId, 'should be able to create message with blobs');
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
await t.throwsAsync(
|
||||
createCopilotMessage(app, randomUUID()),
|
||||
|
||||
@@ -490,19 +490,53 @@ export async function createCopilotMessage(
|
||||
sessionId: string,
|
||||
content?: string,
|
||||
attachments?: string[],
|
||||
blobs?: ArrayBuffer[],
|
||||
blobs?: File[],
|
||||
params?: Record<string, string>
|
||||
): Promise<string> {
|
||||
const res = await app.gql(
|
||||
`
|
||||
mutation createCopilotMessage($options: CreateChatMessageInput!) {
|
||||
createCopilotMessage(options: $options)
|
||||
let resp = app
|
||||
.POST('/graphql')
|
||||
.set({ 'x-request-id': 'test', 'x-operation-name': 'test' })
|
||||
.field(
|
||||
'operations',
|
||||
JSON.stringify({
|
||||
query: `
|
||||
mutation createCopilotMessage($options: CreateChatMessageInput!) {
|
||||
createCopilotMessage(options: $options)
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
options: { sessionId, content, attachments, blobs: [], params },
|
||||
},
|
||||
})
|
||||
)
|
||||
.field(
|
||||
'map',
|
||||
JSON.stringify(
|
||||
Array.from<any>({ length: blobs?.length ?? 0 }).reduce(
|
||||
(acc, _, idx) => {
|
||||
acc[idx.toString()] = [`variables.options.blobs.${idx}`];
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
)
|
||||
)
|
||||
);
|
||||
if (blobs && blobs.length) {
|
||||
for (const [idx, file] of blobs.entries()) {
|
||||
resp = resp.attach(
|
||||
idx.toString(),
|
||||
Buffer.from(await file.arrayBuffer()),
|
||||
{
|
||||
filename: file.name || `file${idx}`,
|
||||
contentType: file.type || 'application/octet-stream',
|
||||
}
|
||||
);
|
||||
}
|
||||
`,
|
||||
{ options: { sessionId, content, attachments, blobs, params } }
|
||||
);
|
||||
}
|
||||
|
||||
return res.createCopilotMessage;
|
||||
const res = await resp.expect(200);
|
||||
|
||||
return res.body.data.createCopilotMessage;
|
||||
}
|
||||
|
||||
export async function chatWithText(
|
||||
|
||||
@@ -138,7 +138,15 @@ export class FalProvider
|
||||
);
|
||||
return {
|
||||
model_name: options.modelName || undefined,
|
||||
image_url: attachments?.[0],
|
||||
image_url: attachments
|
||||
?.map(v =>
|
||||
typeof v === 'string'
|
||||
? v
|
||||
: v.mimeType.startsWith('image/')
|
||||
? v.attachment
|
||||
: undefined
|
||||
)
|
||||
.filter(v => !!v)[0],
|
||||
prompt: content.trim(),
|
||||
loras: lora.length ? lora : undefined,
|
||||
controlnets: controlnets.length ? controlnets : undefined,
|
||||
|
||||
@@ -51,7 +51,15 @@ export const ChatMessageRole = Object.values(AiPromptRole) as [
|
||||
|
||||
export const PureMessageSchema = z.object({
|
||||
content: z.string(),
|
||||
attachments: z.array(z.string()).optional().nullable(),
|
||||
attachments: z
|
||||
.array(
|
||||
z.union([
|
||||
z.string(),
|
||||
z.object({ attachment: z.string(), mimeType: z.string() }),
|
||||
])
|
||||
)
|
||||
.optional()
|
||||
.nullable(),
|
||||
params: z.record(z.any()).optional().nullable(),
|
||||
});
|
||||
|
||||
|
||||
@@ -35,15 +35,26 @@ const FORMAT_INFER_MAP: Record<string, string> = {
|
||||
flv: 'video/flv',
|
||||
};
|
||||
|
||||
function inferMimeType(url: string) {
|
||||
async function inferMimeType(url: string) {
|
||||
if (url.startsWith('data:')) {
|
||||
return url.split(';')[0].split(':')[1];
|
||||
}
|
||||
const extension = url.split('.').pop();
|
||||
const pathname = new URL(url).pathname;
|
||||
const extension = pathname.split('.').pop();
|
||||
if (extension) {
|
||||
return FORMAT_INFER_MAP[extension];
|
||||
const ext = FORMAT_INFER_MAP[extension];
|
||||
if (ext) {
|
||||
return ext;
|
||||
}
|
||||
const mimeType = await fetch(url, {
|
||||
method: 'HEAD',
|
||||
redirect: 'follow',
|
||||
}).then(res => res.headers.get('Content-Type'));
|
||||
if (mimeType) {
|
||||
return mimeType;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
return 'application/octet-stream';
|
||||
}
|
||||
|
||||
export async function chatToGPTMessage(
|
||||
@@ -66,19 +77,24 @@ export async function chatToGPTMessage(
|
||||
contents.push({ type: 'text', text: content });
|
||||
}
|
||||
|
||||
for (const url of attachments) {
|
||||
if (SIMPLE_IMAGE_URL_REGEX.test(url)) {
|
||||
const mimeType =
|
||||
typeof mimetype === 'string' ? mimetype : inferMimeType(url);
|
||||
if (mimeType) {
|
||||
if (mimeType.startsWith('image/')) {
|
||||
contents.push({ type: 'image', image: url, mimeType });
|
||||
} else {
|
||||
const data = url.startsWith('data:')
|
||||
? await fetch(url).then(r => r.arrayBuffer())
|
||||
: new URL(url);
|
||||
contents.push({ type: 'file' as const, data, mimeType });
|
||||
}
|
||||
for (let attachment of attachments) {
|
||||
let mimeType: string;
|
||||
if (typeof attachment === 'string') {
|
||||
mimeType =
|
||||
typeof mimetype === 'string'
|
||||
? mimetype
|
||||
: await inferMimeType(attachment);
|
||||
} else {
|
||||
({ attachment, mimeType } = attachment);
|
||||
}
|
||||
if (SIMPLE_IMAGE_URL_REGEX.test(attachment)) {
|
||||
if (mimeType.startsWith('image/')) {
|
||||
contents.push({ type: 'image', image: attachment, mimeType });
|
||||
} else {
|
||||
const data = attachment.startsWith('data:')
|
||||
? await fetch(attachment).then(r => r.arrayBuffer())
|
||||
: new URL(attachment);
|
||||
contents.push({ type: 'file' as const, data, mimeType });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import { Admin } from '../../core/common';
|
||||
import { AccessController } from '../../core/permission';
|
||||
import { UserType } from '../../core/user';
|
||||
import { PromptService } from './prompt';
|
||||
import { PromptMessage } from './providers';
|
||||
import { ChatSessionService } from './session';
|
||||
import { CopilotStorage } from './storage';
|
||||
import {
|
||||
@@ -113,7 +114,7 @@ class CreateChatMessageInput implements Omit<SubmittedMessage, 'content'> {
|
||||
@Field(() => String, { nullable: true })
|
||||
content!: string | undefined;
|
||||
|
||||
@Field(() => [String], { nullable: true })
|
||||
@Field(() => [String], { nullable: true, deprecationReason: 'use blobs' })
|
||||
attachments!: string[] | undefined;
|
||||
|
||||
@Field(() => [GraphQLUpload], { nullable: true })
|
||||
@@ -527,8 +528,8 @@ export class CopilotResolver {
|
||||
throw new BadRequestException('Session not found');
|
||||
}
|
||||
|
||||
const attachments: PromptMessage['attachments'] = options.attachments || [];
|
||||
if (options.blobs) {
|
||||
options.attachments = options.attachments || [];
|
||||
const { workspaceId } = session.config;
|
||||
|
||||
const blobs = await Promise.all(options.blobs);
|
||||
@@ -539,18 +540,18 @@ export class CopilotResolver {
|
||||
const filename = createHash('sha256')
|
||||
.update(uploaded.buffer)
|
||||
.digest('base64url');
|
||||
const link = await this.storage.put(
|
||||
const attachment = await this.storage.put(
|
||||
user.id,
|
||||
workspaceId,
|
||||
filename,
|
||||
uploaded.buffer
|
||||
);
|
||||
options.attachments.push(link);
|
||||
attachments.push({ attachment, mimeType: blob.mimetype });
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return await this.chatSession.createMessage(options);
|
||||
return await this.chatSession.createMessage({ ...options, attachments });
|
||||
} catch (e: any) {
|
||||
throw new CopilotFailedToCreateMessage(e.message);
|
||||
}
|
||||
|
||||
@@ -166,7 +166,11 @@ export class ChatSession implements AsyncDisposable {
|
||||
firstMessage.attachments || [],
|
||||
]
|
||||
.flat()
|
||||
.filter(v => !!v?.trim());
|
||||
.filter(v =>
|
||||
typeof v === 'string'
|
||||
? !!v.trim()
|
||||
: v && v.attachment.trim() && v.mimeType
|
||||
);
|
||||
|
||||
return finished;
|
||||
}
|
||||
@@ -553,7 +557,12 @@ export class ChatSessionService {
|
||||
action: prompt.action || null,
|
||||
tokens: tokenCost,
|
||||
createdAt,
|
||||
messages: preload.concat(ret.data),
|
||||
messages: preload.concat(ret.data).map(m => ({
|
||||
...m,
|
||||
attachments: m.attachments
|
||||
?.map(a => (typeof a === 'string' ? a : a.attachment))
|
||||
.filter(a => !!a),
|
||||
})),
|
||||
};
|
||||
} else {
|
||||
this.logger.error(
|
||||
|
||||
@@ -265,40 +265,47 @@ export class DataStruct {
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
using _ = await this.measure(`query[${query.type}]`);
|
||||
if (query.type === 'match') {
|
||||
const iidx = this.invertedIndex.get(table)?.get(query.field as string);
|
||||
if (!iidx) {
|
||||
return new Match();
|
||||
|
||||
const result = await (async () => {
|
||||
using _ = await this.measure(`query[${query.type}]`);
|
||||
if (query.type === 'match') {
|
||||
const iidx = this.invertedIndex.get(table)?.get(query.field as string);
|
||||
if (!iidx) {
|
||||
return new Match();
|
||||
}
|
||||
return await iidx.match(trx, query.match);
|
||||
} else if (query.type === 'boolean') {
|
||||
const weights = [];
|
||||
for (const q of query.queries) {
|
||||
weights.push(await this.queryRaw(trx, table, q, cache));
|
||||
}
|
||||
if (query.occur === 'must') {
|
||||
return weights.reduce((acc, w) => acc.and(w));
|
||||
} else if (query.occur === 'must_not') {
|
||||
const total = weights.reduce((acc, w) => acc.and(w));
|
||||
return (await this.matchAll(trx, table)).exclude(total);
|
||||
} else if (query.occur === 'should') {
|
||||
return weights.reduce((acc, w) => acc.or(w));
|
||||
}
|
||||
} else if (query.type === 'all') {
|
||||
return await this.matchAll(trx, table);
|
||||
} else if (query.type === 'boost') {
|
||||
return (await this.queryRaw(trx, table, query.query, cache)).boost(
|
||||
query.boost
|
||||
);
|
||||
} else if (query.type === 'exists') {
|
||||
const iidx = this.invertedIndex.get(table)?.get(query.field as string);
|
||||
if (!iidx) {
|
||||
return new Match();
|
||||
}
|
||||
return await iidx.all(trx);
|
||||
}
|
||||
return await iidx.match(trx, query.match);
|
||||
} else if (query.type === 'boolean') {
|
||||
const weights = [];
|
||||
for (const q of query.queries) {
|
||||
weights.push(await this.queryRaw(trx, table, q, cache));
|
||||
}
|
||||
if (query.occur === 'must') {
|
||||
return weights.reduce((acc, w) => acc.and(w));
|
||||
} else if (query.occur === 'must_not') {
|
||||
const total = weights.reduce((acc, w) => acc.and(w));
|
||||
return (await this.matchAll(trx, table)).exclude(total);
|
||||
} else if (query.occur === 'should') {
|
||||
return weights.reduce((acc, w) => acc.or(w));
|
||||
}
|
||||
} else if (query.type === 'all') {
|
||||
return await this.matchAll(trx, table);
|
||||
} else if (query.type === 'boost') {
|
||||
return (await this.queryRaw(trx, table, query.query, cache)).boost(
|
||||
query.boost
|
||||
);
|
||||
} else if (query.type === 'exists') {
|
||||
const iidx = this.invertedIndex.get(table)?.get(query.field as string);
|
||||
if (!iidx) {
|
||||
return new Match();
|
||||
}
|
||||
return await iidx.all(trx);
|
||||
}
|
||||
throw new Error(`Query type '${query.type}' not supported`);
|
||||
throw new Error(`Query type '${query.type}' not supported`);
|
||||
})();
|
||||
|
||||
cache.set(query, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async clear(trx: DataStructRWTransaction) {
|
||||
|
||||
@@ -225,17 +225,26 @@ export class FullTextInvertedIndex implements InvertedIndex {
|
||||
)?.value ?? 0;
|
||||
for (const token of queryTokens) {
|
||||
const key = InvertedIndexKey.forString(this.fieldKey, token.term);
|
||||
const objs = await trx
|
||||
.objectStore('invertedIndex')
|
||||
.index('key')
|
||||
.getAll(
|
||||
IDBKeyRange.bound(
|
||||
[this.table, key.buffer()],
|
||||
[this.table, key.add1().buffer()],
|
||||
false,
|
||||
true
|
||||
)
|
||||
);
|
||||
const objs = [
|
||||
// match exact
|
||||
await trx
|
||||
.objectStore('invertedIndex')
|
||||
.index('key')
|
||||
.get([this.table, key.buffer()]),
|
||||
// match prefix
|
||||
...(await trx
|
||||
.objectStore('invertedIndex')
|
||||
.index('key')
|
||||
.getAll(
|
||||
IDBKeyRange.bound(
|
||||
[this.table, key.buffer()],
|
||||
[this.table, key.add1().buffer()],
|
||||
true,
|
||||
true
|
||||
),
|
||||
5000 // get maximum 5000 items for prefix match
|
||||
)),
|
||||
];
|
||||
const submatched: {
|
||||
nid: number;
|
||||
score: number;
|
||||
@@ -245,6 +254,9 @@ export class FullTextInvertedIndex implements InvertedIndex {
|
||||
};
|
||||
}[] = [];
|
||||
for (const obj of objs) {
|
||||
if (!obj) {
|
||||
continue;
|
||||
}
|
||||
const key = InvertedIndexKey.fromBuffer(obj.key);
|
||||
const originTokenTerm = key.asString();
|
||||
const matchLength = token.term.length;
|
||||
|
||||
@@ -531,6 +531,7 @@ export function startRecording(
|
||||
|
||||
// set a timeout to stop the recording after MAX_DURATION_FOR_TRANSCRIPTION
|
||||
setTimeout(() => {
|
||||
const state = recordingStateMachine.status$.value;
|
||||
if (
|
||||
state?.status === 'recording' &&
|
||||
state.id === recordingStatus$.value?.id
|
||||
@@ -780,6 +781,13 @@ export const checkMeetingPermissions = () => {
|
||||
) as Record<(typeof mediaTypes)[number], boolean>;
|
||||
};
|
||||
|
||||
export const askForMeetingPermission = async (type: 'microphone') => {
|
||||
if (!isMacOS()) {
|
||||
return false;
|
||||
}
|
||||
return systemPreferences.askForMediaAccess(type);
|
||||
};
|
||||
|
||||
export const checkCanRecordMeeting = () => {
|
||||
const features = checkMeetingPermissions();
|
||||
return (
|
||||
|
||||
@@ -9,6 +9,7 @@ import { shell } from 'electron';
|
||||
import { isMacOS } from '../../shared/utils';
|
||||
import type { NamespaceHandlers } from '../type';
|
||||
import {
|
||||
askForMeetingPermission,
|
||||
checkMeetingPermissions,
|
||||
checkRecordingAvailable,
|
||||
disableRecordingFeature,
|
||||
@@ -76,6 +77,9 @@ export const recordingHandlers = {
|
||||
checkMeetingPermissions: async () => {
|
||||
return checkMeetingPermissions();
|
||||
},
|
||||
askForMeetingPermission: async (_, type: 'microphone') => {
|
||||
return askForMeetingPermission(type);
|
||||
},
|
||||
showRecordingPermissionSetting: async (_, type: 'screen' | 'microphone') => {
|
||||
const urlMap = {
|
||||
screen: 'Privacy_ScreenCapture',
|
||||
|
||||
@@ -260,6 +260,8 @@ export class ChatPanelAddPopover extends SignalWatcher(
|
||||
@query('.search-input')
|
||||
accessor searchInput!: HTMLInputElement;
|
||||
|
||||
private _menuGroupAbortController = new AbortController();
|
||||
|
||||
override connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._updateSearchGroup();
|
||||
@@ -273,6 +275,7 @@ export class ChatPanelAddPopover extends SignalWatcher(
|
||||
override disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
document.removeEventListener('keydown', this._handleKeyDown);
|
||||
this._menuGroupAbortController.abort();
|
||||
}
|
||||
|
||||
override render() {
|
||||
@@ -385,13 +388,15 @@ export class ChatPanelAddPopover extends SignalWatcher(
|
||||
}
|
||||
|
||||
private _updateSearchGroup() {
|
||||
this._menuGroupAbortController.abort();
|
||||
this._menuGroupAbortController = new AbortController();
|
||||
switch (this._mode) {
|
||||
case AddPopoverMode.Tags: {
|
||||
this._searchGroups = [
|
||||
this.searchMenuConfig.getTagMenuGroup(
|
||||
this._query,
|
||||
this._addTagChip,
|
||||
this.abortController.signal
|
||||
this._menuGroupAbortController.signal
|
||||
),
|
||||
];
|
||||
break;
|
||||
@@ -401,7 +406,7 @@ export class ChatPanelAddPopover extends SignalWatcher(
|
||||
this.searchMenuConfig.getCollectionMenuGroup(
|
||||
this._query,
|
||||
this._addCollectionChip,
|
||||
this.abortController.signal
|
||||
this._menuGroupAbortController.signal
|
||||
),
|
||||
];
|
||||
break;
|
||||
@@ -410,7 +415,7 @@ export class ChatPanelAddPopover extends SignalWatcher(
|
||||
const docGroup = this.searchMenuConfig.getDocMenuGroup(
|
||||
this._query,
|
||||
this._addDocChip,
|
||||
this.abortController.signal
|
||||
this._menuGroupAbortController.signal
|
||||
);
|
||||
if (!this._query) {
|
||||
this._searchGroups = [docGroup];
|
||||
@@ -418,12 +423,12 @@ export class ChatPanelAddPopover extends SignalWatcher(
|
||||
const tagGroup = this.searchMenuConfig.getTagMenuGroup(
|
||||
this._query,
|
||||
this._addTagChip,
|
||||
this.abortController.signal
|
||||
this._menuGroupAbortController.signal
|
||||
);
|
||||
const collectionGroup = this.searchMenuConfig.getCollectionMenuGroup(
|
||||
this._query,
|
||||
this._addCollectionChip,
|
||||
this.abortController.signal
|
||||
this._menuGroupAbortController.signal
|
||||
);
|
||||
const nothing = html``;
|
||||
this._searchGroups = [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createLitPortal } from '@blocksuite/affine/components/portal';
|
||||
import { WithDisposable } from '@blocksuite/affine/global/lit';
|
||||
import { ColorScheme } from '@blocksuite/affine/model';
|
||||
import { ThemeProvider } from '@blocksuite/affine/shared/services';
|
||||
import {
|
||||
EditorHost,
|
||||
PropTypes,
|
||||
@@ -112,6 +112,7 @@ export class AIItemList extends WithDisposable(LitElement) {
|
||||
}
|
||||
|
||||
override render() {
|
||||
const theme = this.host.std.get(ThemeProvider).app$.value;
|
||||
return html`${repeat(this.groups, group => {
|
||||
return html`
|
||||
${group.name
|
||||
@@ -124,7 +125,7 @@ export class AIItemList extends WithDisposable(LitElement) {
|
||||
item => item.name,
|
||||
item =>
|
||||
html`<ai-item
|
||||
.theme=${this.theme}
|
||||
.theme=${theme}
|
||||
.onClick=${this.onClick}
|
||||
.item=${item}
|
||||
.host=${this.host}
|
||||
@@ -147,9 +148,6 @@ export class AIItemList extends WithDisposable(LitElement) {
|
||||
|
||||
@property({ attribute: 'data-testid', reflect: true })
|
||||
accessor testId = 'ai-item-list';
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor theme: ColorScheme = ColorScheme.Light;
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -86,10 +86,8 @@ export class AskAIPanel extends WithDisposable(LitElement) {
|
||||
const style = styleMap({
|
||||
minWidth: `${this.minWidth}px`,
|
||||
});
|
||||
const appTheme = this.host.std.get(ThemeProvider).app$.value;
|
||||
return html`<div class="ask-ai-panel" style=${style}>
|
||||
<ai-item-list
|
||||
.theme=${appTheme}
|
||||
.host=${this.host}
|
||||
.groups=${this._actionGroups}
|
||||
.onClick=${this.onItemClick}
|
||||
|
||||
@@ -8,7 +8,11 @@ import { PageEditorBlockSpecs } from '@blocksuite/affine/extensions';
|
||||
import { Container, type ServiceProvider } from '@blocksuite/affine/global/di';
|
||||
import { WithDisposable } from '@blocksuite/affine/global/lit';
|
||||
import { codeBlockWrapMiddleware } from '@blocksuite/affine/shared/adapters';
|
||||
import { LinkPreviewerService } from '@blocksuite/affine/shared/services';
|
||||
import {
|
||||
LinkPreviewerService,
|
||||
ThemeProvider,
|
||||
} from '@blocksuite/affine/shared/services';
|
||||
import { unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
|
||||
import {
|
||||
BlockStdScope,
|
||||
BlockViewIdentifier,
|
||||
@@ -22,7 +26,11 @@ import type {
|
||||
Store,
|
||||
TransformerMiddleware,
|
||||
} from '@blocksuite/affine/store';
|
||||
import { css, html, nothing, type PropertyValues } from 'lit';
|
||||
import {
|
||||
darkCssVariablesV2,
|
||||
lightCssVariablesV2,
|
||||
} from '@toeverything/theme/v2';
|
||||
import { css, html, nothing, type PropertyValues, unsafeCSS } from 'lit';
|
||||
import { property, query } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { keyed } from 'lit/directives/keyed.js';
|
||||
@@ -109,7 +117,7 @@ export class TextRenderer extends WithDisposable(ShadowlessElement) {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
line-height: var(--affine-line-height);
|
||||
color: var(--affine-text-primary-color);
|
||||
color: ${unsafeCSSVarV2('text/primary')};
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
@@ -168,6 +176,18 @@ export class TextRenderer extends WithDisposable(ShadowlessElement) {
|
||||
}
|
||||
}
|
||||
|
||||
.text-renderer-container[data-app-theme='dark'] {
|
||||
.ai-answer-text-editor .affine-page-root-block-container {
|
||||
color: ${unsafeCSS(darkCssVariablesV2['--affine-v2-text-primary'])};
|
||||
}
|
||||
}
|
||||
|
||||
.text-renderer-container[data-app-theme='light'] {
|
||||
.ai-answer-text-editor .affine-page-root-block-container {
|
||||
color: ${unsafeCSS(lightCssVariablesV2['--affine-v2-text-primary'])};
|
||||
}
|
||||
}
|
||||
|
||||
${customHeadingStyles}
|
||||
`;
|
||||
|
||||
@@ -288,8 +308,9 @@ export class TextRenderer extends WithDisposable(ShadowlessElement) {
|
||||
'text-renderer-container': true,
|
||||
'custom-heading': !!customHeading,
|
||||
});
|
||||
const theme = this.host?.std.get(ThemeProvider).app$.value;
|
||||
return html`
|
||||
<div class=${classes} data-testid=${testId}>
|
||||
<div class=${classes} data-testid=${testId} data-app-theme=${theme}>
|
||||
${keyed(
|
||||
this._doc,
|
||||
html`<div class="ai-answer-text-editor affine-page-viewport">
|
||||
|
||||
@@ -30,9 +30,3 @@ export const notesButtonIcon = style({
|
||||
export const error = style({
|
||||
color: cssVarV2('aI/errorText'),
|
||||
});
|
||||
|
||||
export const publicUserLabel = style({
|
||||
fontSize: cssVar('fontXs'),
|
||||
fontWeight: 500,
|
||||
userSelect: 'none',
|
||||
});
|
||||
|
||||
@@ -37,12 +37,12 @@ export function patchQuickSearchService(framework: FrameworkProvider) {
|
||||
searchResult = await new Promise((resolve, reject) =>
|
||||
framework.get(QuickSearchService).quickSearch.show(
|
||||
[
|
||||
framework.get(RecentDocsQuickSearchSession),
|
||||
framework.get(CreationQuickSearchSession),
|
||||
framework.get(DocsQuickSearchSession),
|
||||
framework.get(LinksQuickSearchSession),
|
||||
framework.get(ExternalLinksQuickSearchSession),
|
||||
framework.get(JournalsQuickSearchSession),
|
||||
framework.createEntity(RecentDocsQuickSearchSession),
|
||||
framework.createEntity(CreationQuickSearchSession),
|
||||
framework.createEntity(DocsQuickSearchSession),
|
||||
framework.createEntity(LinksQuickSearchSession),
|
||||
framework.createEntity(ExternalLinksQuickSearchSession),
|
||||
framework.createEntity(JournalsQuickSearchSession),
|
||||
],
|
||||
result => {
|
||||
if (result === null) {
|
||||
|
||||
@@ -210,7 +210,13 @@ export const MeetingsSettings = () => {
|
||||
|
||||
const handleOpenMicrophoneRecordingPermissionSetting =
|
||||
useAsyncCallback(async () => {
|
||||
await meetingSettingsService.showRecordingPermissionSetting('microphone');
|
||||
const result =
|
||||
await meetingSettingsService.askForMeetingPermission('microphone');
|
||||
if (!result) {
|
||||
await meetingSettingsService.showRecordingPermissionSetting(
|
||||
'microphone'
|
||||
);
|
||||
}
|
||||
}, [meetingSettingsService]);
|
||||
|
||||
const handleOpenSavedRecordings = useAsyncCallback(async () => {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { style } from '@vanilla-extract/css';
|
||||
|
||||
export const publicUserLabel = style({
|
||||
fontSize: 'inherit',
|
||||
display: 'flex',
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
});
|
||||
|
||||
|
||||
@@ -242,7 +242,21 @@ export class AudioAttachmentBlock extends Entity<AttachmentBlockModel> {
|
||||
);
|
||||
};
|
||||
|
||||
const fillActions = async (actions: TranscriptionResult['actions']) => {
|
||||
if (!actions) {
|
||||
return;
|
||||
}
|
||||
const calloutId = addCalloutBlock('🎯', 'Todo');
|
||||
await insertFromMarkdown(
|
||||
undefined,
|
||||
actions ?? '',
|
||||
this.props.doc,
|
||||
calloutId,
|
||||
1
|
||||
);
|
||||
};
|
||||
fillTranscription(result.segments);
|
||||
await fillSummary(result.summary);
|
||||
await fillActions(result.actions);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ export class AudioTranscriptionJobStore extends Entity<{
|
||||
}
|
||||
const files = await this.props.getAudioFiles();
|
||||
const response = await graphqlService.gql({
|
||||
timeout: 600_000, // default 15s is too short for audio transcription
|
||||
query: submitAudioTranscriptionMutation,
|
||||
variables: {
|
||||
workspaceId: this.currentWorkspaceId,
|
||||
|
||||
@@ -7,4 +7,5 @@ export interface TranscriptionResult {
|
||||
end: string;
|
||||
transcription: string;
|
||||
}[];
|
||||
actions?: string;
|
||||
}
|
||||
|
||||
@@ -188,8 +188,7 @@ export class AudioMediaManagerService extends Service {
|
||||
if (!stats || !currentState) {
|
||||
return;
|
||||
}
|
||||
const seekOffset =
|
||||
currentState.seekOffset + (Date.now() - currentState.updateTime) / 1000;
|
||||
const seekOffset = currentState.seekOffset;
|
||||
this.globalMediaState.updatePlaybackState({
|
||||
state: 'playing',
|
||||
// rewind to the beginning if the seek offset is greater than the duration
|
||||
@@ -207,7 +206,9 @@ export class AudioMediaManagerService extends Service {
|
||||
|
||||
this.globalMediaState.updatePlaybackState({
|
||||
state: 'paused',
|
||||
seekOffset: (Date.now() - state.updateTime) / 1000 + state.seekOffset,
|
||||
seekOffset:
|
||||
((Date.now() - state.updateTime) / 1000) * (state.playbackRate || 1.0) +
|
||||
state.seekOffset,
|
||||
updateTime: Date.now(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -114,6 +114,12 @@ export class MeetingSettingsService extends Service {
|
||||
);
|
||||
}
|
||||
|
||||
async askForMeetingPermission(type: 'microphone') {
|
||||
return this.desktopApiService?.handler.recording.askForMeetingPermission(
|
||||
type
|
||||
);
|
||||
}
|
||||
|
||||
setRecordingMode = (mode: MeetingSettingsSchema['recordingMode']) => {
|
||||
const currentMode = this.settings.recordingMode;
|
||||
|
||||
|
||||
@@ -116,4 +116,8 @@ export class DocsQuickSearchSession
|
||||
setQuery(query: string) {
|
||||
this.query$.next(query);
|
||||
}
|
||||
|
||||
override dispose(): void {
|
||||
this.query.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,9 +152,6 @@ export class SearchMenuService extends Service {
|
||||
},
|
||||
{
|
||||
fields: ['docId', 'title'],
|
||||
pagination: {
|
||||
limit: 1,
|
||||
},
|
||||
highlights: [
|
||||
{
|
||||
field: 'title',
|
||||
|
||||
Binary file not shown.
@@ -54,6 +54,6 @@ test('can add text property', async ({ page }) => {
|
||||
await page.getByTestId('mobile-menu-back-button').last().click();
|
||||
|
||||
await expect(page.getByTestId('mobile-menu-back-button')).toContainText(
|
||||
'How to use folder and Tags'
|
||||
'Getting Started'
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user