Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/ai configure panel #46

Merged
merged 2 commits into from
Apr 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@ declare module 'vue' {
export interface GlobalComponents {
AppProvider: typeof import('./src/components/AppProvider.vue')['default']
NButton: typeof import('naive-ui')['NButton']
NButtonGroup: typeof import('naive-ui')['NButtonGroup']
NCard: typeof import('naive-ui')['NCard']
'NCard:': typeof import('naive-ui')['NCard:']
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
NDialogProvider: typeof import('naive-ui')['NDialogProvider']
NDropdown: typeof import('naive-ui')['NDropdown']
NForm: typeof import('naive-ui')['NForm']
NFormItem: typeof import('naive-ui')['NFormItem']
NFormItemRow: typeof import('naive-ui')['NFormItemRow']
NGi: typeof import('naive-ui')['NGi']
NGrid: typeof import('naive-ui')['NGrid']
NGridItem: typeof import('naive-ui')['NGridItem']
Expand Down
3 changes: 3 additions & 0 deletions src/electron/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ contextBridge.exposeInMainWorld('storeAPI', {
ipcRenderer.invoke('storeAPI', { method: 'GET', key, value: defaultValue }),
set: async (key: string, value: unknown) =>
ipcRenderer.invoke('storeAPI', { method: 'SET', key, value }),
getSecret: async (key: string) => ipcRenderer.invoke('storeAPI', { method: 'GET_SECRET', key }),
setSecret: async (key: string, value: unknown) =>
ipcRenderer.invoke('storeAPI', { method: 'SET_SECRET', key, value }),
});

contextBridge.exposeInMainWorld('sourceFileAPI', {
Expand Down
46 changes: 46 additions & 0 deletions src/electron/storeApi.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,70 @@
import Electron from 'electron';
import Store from 'electron-store';
import * as os from 'node:os';
import crypto from 'crypto';

export enum StoreApiMethods {
GET = 'GET',
SET = 'SET',
GET_SECRET = 'GET_SECRET',
SET_SECRET = 'SET_SECRET',
}
export type StoreApiInput = {
method: StoreApiMethods;
key: string;
value: unknown;
};

const getMacAddress = (): string | undefined => {
const networkInterfaces = os.networkInterfaces();

for (const name of Object.keys(networkInterfaces)) {
for (const net of networkInterfaces[name]!) {

Check warning on line 22 in src/electron/storeApi.ts

View workflow job for this annotation

GitHub Actions / build (macos-latest, 20.x)

Forbidden non-null assertion

Check warning on line 22 in src/electron/storeApi.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 20.x)

Forbidden non-null assertion

Check warning on line 22 in src/electron/storeApi.ts

View workflow job for this annotation

GitHub Actions / build (windows-latest, 20.x)

Forbidden non-null assertion
// Skip over non-ipv4 and internal (i.e. 127.0.0.1) addresses
if (net.family === 'IPv4' && !net.internal) {
return net.mac;
}
}
}
};
const store = new Store();

const macAddress = getMacAddress();
const secretKey = crypto.createHash('sha256').update(macAddress).digest();
const iv = Buffer.from('dockitse'.repeat(2), 'utf8');
const encryptValue = (value: string) => {
const cipher = crypto.createCipheriv('aes-256-cbc', secretKey, iv);
let encrypted = cipher.update(value, 'utf8', 'hex');
encrypted += cipher.final('hex');
return encrypted;
};

const decryptValue = (encryptedValue: string) => {
const decipher = crypto.createDecipheriv('aes-256-cbc', secretKey, iv);
let decrypted = decipher.update(encryptedValue, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
};

const storeApi: { [key: string]: (key: string, val: unknown) => unknown } = {
get: (key: string, defaultValue: unknown) => store.get(key, defaultValue),

set: (key: string, value: unknown) => {
store.set(key, value);
},

get_secret: (key: string) => {
const encryptedValue = store.get(key, '');

return encryptedValue ? JSON.parse(decryptValue(encryptedValue as string)) : undefined;
},

set_secret: (key: string, value: unknown) => {
const encryptedValue = encryptValue(JSON.stringify(value));
store.set(key, encryptedValue);
},
};

export const registerStoreApiListener = (ipcMain: Electron.IpcMain) => {
ipcMain.handle('storeAPI', (_, { method, key, value }: StoreApiInput) =>
storeApi[method.toLowerCase()](key, value),
Expand Down
7 changes: 7 additions & 0 deletions src/lang/enUS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ export const enUS = {
auto: 'Follow OS',
dark: 'Dark Theme',
light: 'Light Theme',
ai: {
title: 'GPTs',
others: 'Other GPTs',
model: 'Model',
apiKey: 'API Key',
prompt: 'Prompt',
},
},
connection: {
new: 'New connection',
Expand Down
7 changes: 7 additions & 0 deletions src/lang/zhCN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ export const zhCN = {
auto: '跟随系统',
dark: '暗黑主题',
light: '月白主题',
ai: {
title: 'GPTs配置',
others: '其他GPTs',
model: '模型',
apiKey: 'API密钥',
prompt: '提示词',
},
},
connection: {
new: '新建连接',
Expand Down
15 changes: 15 additions & 0 deletions src/store/appStore.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,43 @@
import { defineStore } from 'pinia';
import { pureObject } from '../common';

export enum ThemeType {
AUTO = 'auto',
DARK = 'dark',
LIGHT = 'light',
}

export enum LanguageType {
AUTO = 'auto',
ZH_CN = 'zhCN',
EN_US = 'enUS',
}
const { storeAPI } = window;
export const useAppStore = defineStore('app', {
state: (): {
themeType: ThemeType;
languageType: LanguageType;
connectPanel: boolean;
uiThemeType: Exclude<ThemeType, ThemeType.AUTO>;
skipVersion: string;
aigcConfig: {
openAi: { apiKey?: string; model?: string; prompt?: string };
};
} => {
return {
themeType: ThemeType.AUTO,
languageType: LanguageType.AUTO,
connectPanel: true, //
uiThemeType: ThemeType.LIGHT,
skipVersion: '',
aigcConfig: { openAi: {} },
};
},
persist: true,
actions: {
async fetchAigcConfig() {
this.aigcConfig = await storeAPI.getSecret('aigcConfig', { openAi: {} });
},
setConnectPanel() {
this.connectPanel = !this.connectPanel;
},
Expand All @@ -49,5 +60,9 @@ export const useAppStore = defineStore('app', {
getEditorTheme() {
return this.uiThemeType === ThemeType.DARK ? 'vs-dark' : 'vs-light';
},
async saveAigcConfig(config: { [key: string]: unknown }) {
this.aigcConfig = config;
await storeAPI.setSecret('aigcConfig', pureObject(config));
},
},
});
73 changes: 73 additions & 0 deletions src/views/setting/components/about-us.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<template>
<div class="about-us">
<h1 class="title">About Us</h1>
<div class="section">
<h2 class="subtitle">DocKit</h2>
<p>
DocKit is a modern cross-platform NoSQL/NewSQL GUI client. Explore your data any time from
your Mac, Windows, and Linux.
</p>
</div>
<div class="section">
<h2 class="subtitle">Features</h2>
<ul>
<li>
Full-featured editor, powered by monaco-editor, the backbone of VSCode, providing a
familiar editor environment for developers.
</li>
<li>Keep your connections in desktop apps, moving the dependencies of dashboard tools.</li>
<li>
File persistence, allowing you to save your code in your machine as a file, never lost.
</li>
<li>Supports multiple engines including Elasticsearch, OpenSearch, and more to come.</li>
</ul>
</div>
<div class="section">
<h2 class="subtitle">License</h2>
<p>DocKit is an open-source project under the Apache 2.0 License.</p>
</div>
</div>
</template>

<script setup lang="ts">
// Add any necessary TypeScript code here
</script>

<style lang="scss" scoped>
@keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}

.about-us {
animation: fadeIn 2s ease-in;

.title {
font-size: 2em;
text-align: center;
margin-bottom: 1em;
}

.section {
margin-bottom: 2em;

.subtitle {
font-size: 1.5em;
}

p,
ul {
font-size: 1.2em;
line-height: 1.5;
}

ul {
padding-left: 1em;
}
}
}
</style>
58 changes: 58 additions & 0 deletions src/views/setting/components/aigc.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<template>
<div>
<n-tabs type="line" animated>
<n-tab-pane name="OpenAI" tab="OpenAI">
<n-form class="form-tab-pane">
<n-form-item-row :label="$t('setting.ai.model')">
<n-input v-model:value="openAi.model" />
</n-form-item-row>
<n-form-item-row :label="$t('setting.ai.apiKey')">
<n-input type="password" show-password-on="click" v-model:value="openAi.apiKey" />
</n-form-item-row>
<n-form-item-row :label="$t('setting.ai.prompt')">
<n-input type="textarea" v-model:value="openAi.prompt" />
</n-form-item-row>
<n-button type="error" @click="reset" class="action-button">Cancel</n-button>
<n-button type="success" @click="save" class="action-button">Save</n-button>
<n-button type="primary" @click="enable" class="action-button">Enable</n-button>
</n-form>
</n-tab-pane>
<n-tab-pane :name="$t('setting.ai.others')" :tab="$t('setting.ai.others')">
Coming soon
</n-tab-pane>
</n-tabs>
</div>
</template>

<script setup lang="ts">
import { useAppStore } from '../../../store';
import { storeToRefs } from 'pinia';
const appStore = useAppStore();
const { fetchAigcConfig, saveAigcConfig } = appStore;
const { aigcConfig } = storeToRefs(appStore);

const openAi = ref({ ...aigcConfig.value.openAi });
const reset = () => {
openAi.value = { apiKey: '', model: '', prompt: '' };
aigcConfig.value.openAi = openAi.value;
};

const save = async () => {
await saveAigcConfig({ ...aigcConfig.value, openAi: openAi.value });
};

const enable = async () => {
await saveAigcConfig({ ...aigcConfig.value, openAi: openAi.value, enabled: true });
};

fetchAigcConfig();
</script>

<style lang="scss" scoped>
.form-tab-pane {
width: 96%;
.action-button {
margin-right: 10px;
}
}
</style>
8 changes: 7 additions & 1 deletion src/views/setting/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,20 @@
<n-tab-pane n-tab-pane name="Basic" :tab="$t('setting.basic')">
<basic-setting />
</n-tab-pane>
<n-tab-pane n-tab-pane name="AI" :tab="$t('setting.ai.title')">
<aigc />
</n-tab-pane>
<n-tab-pane n-tab-pane name="About" :tab="$t('setting.about')">
<basic-setting />
<about-us />
</n-tab-pane>
</n-tabs>
</div>
</template>

<script setup lang="ts">
import BasicSetting from './components/basic.vue';
import Aigc from './components/aigc.vue';
import AboutUs from './components/about-us.vue';
</script>

<style lang="scss" scoped>
Expand All @@ -25,6 +30,7 @@ import BasicSetting from './components/basic.vue';
padding: 15px 0;
border-right: 1px solid var(--border-color);
}

.n-tabs-nav-scroll-content {
height: 100%;
width: 200px;
Expand Down
2 changes: 2 additions & 0 deletions src/vite-env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export interface IElectronAPI {
export interface IStoreAPI {
get: <T>(key: string, defaultValue: T) => Promise<T>;
set: <T>(key: string, value: T) => Promise<void>;
getSecret: <T>(key: string, defaultValue: T) => Promise<T>;
setSecret: <T>(key: string, value: T) => Promise<void>;
}

export interface ISourceFileAPI {
Expand Down
Loading