Skip to content

Commit

Permalink
Merge pull request #5783 from haiwen/feat/question-answering-search-new
Browse files Browse the repository at this point in the history
Feat/question answering search new
  • Loading branch information
Michael18811380328 authored Nov 23, 2023
2 parents 9bf879a + 3c6eda2 commit 1660835
Show file tree
Hide file tree
Showing 22 changed files with 659 additions and 82 deletions.
17 changes: 17 additions & 0 deletions frontend/src/assets/icons/arrow.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 15 additions & 0 deletions frontend/src/assets/icons/helpful-selected.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 18 additions & 0 deletions frontend/src/assets/icons/helpful.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 15 additions & 0 deletions frontend/src/assets/icons/helpless-selected.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 18 additions & 0 deletions frontend/src/assets/icons/helpless.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions frontend/src/assets/icons/send.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
79 changes: 79 additions & 0 deletions frontend/src/components/search/ai-search-ask.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
.search-container.show.ai-search-ask {
width: 800px;
}

.ai-search-ask .ai-search-ask-header {
display: flex;
align-items: center;
padding: 1rem;
border-bottom: 1px solid rgba(0, 40, 100, 0.12);
}

.ai-search-ask .ai-search-ask-header .ai-search-ask-return {
padding: 0 4px;
transform: rotate(180deg);
line-height: 10px;
cursor: pointer;
}

.ai-search-ask .ai-search-ask-header .ai-search-ask-return .seafile-multicolor-icon-arrow {
opacity: 0.6;
}

.ai-search-ask .ai-search-ask-header .ai-search-ask-return:hover .seafile-multicolor-icon-arrow {
opacity: 0.8;
}

.ai-search-ask .ai-search-ask-body {
display: flex;
max-height: 400px;
overflow-y: auto;
}

.ai-search-ask .ai-search-ask-body .ai-search-ask-body-left {
flex-shrink: 0;
margin-right: 1rem;
}

.ai-search-ask .ai-search-ask-body .ai-search-ask-body-right {
line-height: 1.8;
font-size: 14px;
width: 100%;
}

.ai-search-ask .ai-search-ask-footer {
border-top: 1px solid rgba(0, 40, 100, 0.12);
margin: 0 1rem;
padding: 1rem 0;
}

.ai-search-ask .ai-search-ask-footer .ai-search-ask-footer-btn {
width: 16px;
height: 16px;
position: absolute;
right: 8px;
top: 8px;
background-color: #fff;
cursor: pointer;
}

.ai-search-ask .ai-search-ask-footer .ai-search-ask-footer-btn .seafile-multicolor-icon-send {
color: #ff8000;
}

.ai-search-ask .ai-search-ask-footer .ai-search-ask-footer-btn:hover .seafile-multicolor-icon-send {
color: #d96d00;
}

@media (max-width: 768px) {

.search-container.show.ai-search-ask {
width: 100%;
}

.ai-search-ask .search-input {
box-shadow: none;
width: 100% !important;
}

}
209 changes: 209 additions & 0 deletions frontend/src/components/search/ai-search-ask.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import isHotkey from 'is-hotkey';
import { seafileAPI } from '../../utils/seafile-api';
import { gettext } from '../../utils/constants';
import toaster from '../toast';
import Loading from '../loading';
import Icon from '../icon';
import { Utils } from '../../utils/utils';
import { SEARCH_DELAY_TIME, getValueLength } from './constant';
import AISearchRefrences from './ai-search-widgets/ai-search-refrences';
import AISearchHelp from './ai-search-widgets/ai-search-help';
import AISearchRobot from './ai-search-widgets/ai-search-robot';

import './ai-search-ask.css';

const INDEX_STATE = {
RUNNING: 'running',
UNCREATED: 'uncreated',
FINISHED: 'finished'
};

export default class AISearchAsk extends Component {

static propTypes = {
value: PropTypes.string,
token: PropTypes.string,
repoID: PropTypes.string,
repoName: PropTypes.string,
indexState: PropTypes.string,
onItemClickHandler: PropTypes.func.isRequired,
};

constructor(props) {
super(props);
this.state = {
value: props.value,
isLoading: false,
answeringResult: '',
hitFiles: [],
};
this.timer = null;
this.isChineseInput = false;
}

componentDidMount() {
document.addEventListener('compositionstart', this.onCompositionStart);
document.addEventListener('compositionend', this.onCompositionEnd);
this.onSearch();
}

componentWillUnmount() {
document.removeEventListener('compositionstart', this.onCompositionStart);
document.removeEventListener('compositionend', this.onCompositionEnd);
this.isChineseInput = false;
if (this.timer) {
clearTimeout(this.timer);
this.timer = null;
}
}

onCompositionStart = () => {
this.isChineseInput = true;
if (this.timer) {
clearTimeout(this.timer);
this.timer = null;
}
};

onCompositionEnd = () => {
this.isChineseInput = false;
if (this.timer) {
clearTimeout(this.timer);
this.timer = null;
}
this.timer = setTimeout(() => {
this.onSearch();
}, SEARCH_DELAY_TIME);
};

onChange = (event) => {
const newValue = event.target.value;
this.setState({ value: newValue }, () => {
if (!this.isChineseInput) {
if (this.timer) {
clearTimeout(this.timer);
this.timer = null;
}
this.timer = setTimeout(() => {
this.onSearch();
}, SEARCH_DELAY_TIME);
}
});
};

onKeydown = (event) => {
if (isHotkey('enter', event)) {
this.onSearch();
}
};

formatQuestionAnsweringItems(data) {
let items = [];
for (let i = 0; i < data.length; i++) {
items[i] = {};
items[i]['index'] = [i];
items[i]['name'] = data[i].substring(data[i].lastIndexOf('/')+1);
items[i]['path'] = data[i];
items[i]['repo_id'] = this.props.repoID;
items[i]['is_dir'] = false;
items[i]['link_content'] = decodeURI(data[i]).substring(1);
items[i]['content'] = data[i].sentence;
items[i]['thumbnail_url'] = '';
}
return items;
}

onSearch = () => {
const { indexState, repoID, token } = this.props;
if (indexState === INDEX_STATE.UNCREATED) {
toaster.warning(gettext('Please create index first.'));
return;
}
if (indexState === INDEX_STATE.RUNNING) {
toaster.warning(gettext('Indexing, please try again later.'));
return;
}
if (this.state.isLoading || getValueLength(this.state.value.trim()) < 3) {
return;
}
this.setState({ isLoading: true });
const searchParams = {
q: this.state.value.trim(),
search_repo: repoID || 'all',
};
seafileAPI.questionAnsweringFiles(searchParams, token).then(res => {
const { answering_result } = res.data || {};
const hit_files = answering_result !== 'false' ? res.data.hit_files : [];
this.setState({
isLoading: false,
answeringResult: answering_result === 'false' ? 'No result' : answering_result,
hitFiles: this.formatQuestionAnsweringItems(hit_files),
});
}).catch(error => {
/* eslint-disable */
console.log(error);
this.setState({ isLoading: false });
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
};

render() {
return (
<div className="search">
<div className="search-mask show" onClick={this.props.closeAsk}></div>
<div className="ai-search-ask search-container show p-0">

<div className="ai-search-ask-header">
<span className="ai-search-ask-return" onClick={this.props.closeAsk}>
<Icon symbol='arrow' />
</span>
{gettext('Return')}
</div>

{this.state.isLoading ?
<div className="d-flex align-items-center my-8">
<Loading />
</div>
:
<div className="ai-search-ask-body p-6 pb-4">
<div className="ai-search-ask-body-left">
<AISearchRobot/>
</div>
<div className="ai-search-ask-body-right">
<div>{this.state.answeringResult}</div>
<AISearchHelp />
{this.state.hitFiles.length > 0 &&
<AISearchRefrences
hitFiles={this.state.hitFiles}
onItemClickHandler={this.props.onItemClickHandler}
/>
}
</div>
</div>
}

<div className="ai-search-ask-footer">
<div className={`input-icon mb-1`}>
<input
type="text"
className="form-control search-input w-100"
name="query"
value={this.state.value}
onChange={this.onChange}
autoComplete="off"
onKeyDown={this.onKeydown}
placeholder={gettext('Ask a question') + '...'}
/>
<span className="ai-search-ask-footer-btn" onClick={this.onSearch}>
<Icon symbol='send' />
</span>
</div>
</div>
</div>
</div>
)
}
}
Loading

0 comments on commit 1660835

Please sign in to comment.