├── .gitignore ├── README.md ├── __init__.py ├── data ├── pull_data.sh ├── test.dec ├── test.enc ├── train.dec └── train.enc ├── data_utils.py ├── env_setup.sh ├── execute.py ├── neuralconvo.ini ├── output_example ├── 1.png ├── 2.png ├── 3.png └── 4.png ├── seq2seq.ini ├── seq2seq_model.py ├── seq2seq_serve.ini └── ui ├── .gitignore ├── README.md ├── __init.py__ ├── app.py ├── requirements.txt ├── seq2seq_serve.ini ├── setup.sh ├── static ├── css │ ├── normalize.css │ └── style.css ├── js │ ├── index.js │ ├── jquery-latest.js │ ├── jquery.mCustomScrollbar.concat.min.js │ ├── jquery.mCustomScrollbar.min.css │ └── jquery.min.js ├── res │ ├── botim.png │ ├── hd1.jpg │ └── hd2.jpg └── scss │ └── style.scss └── templates └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chinese song generation 2 | 3 | Using seq2seq with attention to generate chinese song lyrics! 4 | 5 | Main code is from [here](https://github.com/suriyadeepan/easy_seq2seq) 6 | Chinese tokenizer is [jieba](https://github.com/fxsjy/jieba) 7 | 8 | 9 | ## Training 10 | 11 | ```bash 12 | # edit seq2seq.ini file to set 13 | # mode = train 14 | # put lyrics into train.enc, train.dec, (test.enc, test.dec) 15 | python execute.py 16 | # or use custom ini file 17 | # python execute.py my_custom_conf.ini 18 | ``` 19 | 20 | ## Testing 21 | 22 | ```bash 23 | # edit seq2seq.ini file to set 24 | # mode = test 25 | python execute.py 26 | ``` 27 | 28 | ## Serve 29 | 30 | ```bash 31 | # configuration : seq2seq_serve.ini 32 | # move work_dir to ui folder. modify checkpoint to be the checkpoint you want 33 | python ui/app.py 34 | # wait until this message shows up 35 | # "Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)" 36 | # open up the address in browser, chat with the bot 37 | ``` 38 | ## Output examples 39 |  40 |  41 |  42 |  43 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billycyy/chinese_song_generation/6f4c290f0dc6a6d8410084e358c6c64d5c08bdd5/__init__.py -------------------------------------------------------------------------------- /data/pull_data.sh: -------------------------------------------------------------------------------- 1 | wget -c 'https://www.dropbox.com/s/ncfa5t950gvtaeb/test.enc?dl=0' -O test.enc 2 | wget -c 'https://www.dropbox.com/s/48ro4759jaikque/test.dec?dl=0' -O test.dec 3 | wget -c 'https://www.dropbox.com/s/gu54ngk3xpwite4/train.enc?dl=0' -O train.enc 4 | wget -c 'https://www.dropbox.com/s/g3z2msjziqocndl/train.dec?dl=0' -O train.dec 5 | -------------------------------------------------------------------------------- /data/test.dec: -------------------------------------------------------------------------------- 1 | 谁了解阮甘有明啊仔 -------------------------------------------------------------------------------- /data/test.enc: -------------------------------------------------------------------------------- 1 | 前途茫茫亲像大海,为着将来就要赌命赌气慨,这款生活不应该,每日日头若落西 -------------------------------------------------------------------------------- /data/train.dec: -------------------------------------------------------------------------------- 1 | 香槟早挥发得彻底 2 | 白如白蛾潜回红尘俗世 3 | 俯瞰过灵位 4 | 但是爱骤变芥蒂后 5 | 如同肮脏污秽 6 | 不要提沉默带笑玫瑰 7 | 带刺回礼只信任防卫 8 | 怎么冷酷却仍然美丽 9 | 得不到的从来矜贵 10 | 身处劣势如何不攻心计 -------------------------------------------------------------------------------- /data/train.enc: -------------------------------------------------------------------------------- 1 | 白如白牙热情被吞噬 2 | 白如白牙热情被吞噬,香槟早挥发得彻底 3 | 白如白牙热情被吞噬,香槟早挥发得彻底,白如白蛾潜回红尘俗世 4 | 白如白牙热情被吞噬,香槟早挥发得彻底,白如白蛾潜回红尘俗世,俯瞰过灵位 5 | 香槟早挥发得彻底,白如白蛾潜回红尘俗世,俯瞰过灵位,但是爱骤变芥蒂后 6 | 白如白蛾潜回红尘俗世,俯瞰过灵位,但是爱骤变芥蒂后,如同肮脏污秽 7 | 俯瞰过灵位,但是爱骤变芥蒂后,如同肮脏污秽,不要提沉默带笑玫瑰 8 | 但是爱骤变芥蒂后,如同肮脏污秽,不要提沉默带笑玫瑰,带刺回礼只信任防卫 9 | 如同肮脏污秽,不要提沉默带笑玫瑰,带刺回礼只信任防卫,怎么冷酷却仍然美丽 10 | 不要提沉默带笑玫瑰,带刺回礼只信任防卫,怎么冷酷却仍然美丽,得不到的从来矜贵 -------------------------------------------------------------------------------- /data_utils.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 The TensorFlow Authors. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # ============================================================================== 15 | 16 | """Utilities for downloading data from WMT, tokenizing, vocabularies.""" 17 | from __future__ import absolute_import 18 | from __future__ import division 19 | from __future__ import print_function 20 | 21 | import os 22 | import re 23 | import jieba 24 | 25 | from six.moves import urllib 26 | 27 | from tensorflow.python.platform import gfile 28 | 29 | # Special vocabulary symbols - we always put them at the start. 30 | _PAD = b"_PAD" 31 | _GO = b"_GO" 32 | _EOS = b"_EOS" 33 | _UNK = b"_UNK" 34 | _START_VOCAB = [_PAD, _GO, _EOS, _UNK] 35 | 36 | PAD_ID = 0 37 | GO_ID = 1 38 | EOS_ID = 2 39 | UNK_ID = 3 40 | 41 | # Regular expressions used to tokenize. 42 | _WORD_SPLIT = re.compile(b"([.,!?\"':;)(])") 43 | _DIGIT_RE = re.compile(br"\d") 44 | 45 | 46 | 47 | def basic_tokenizer(sentence): 48 | """Very basic tokenizer: split the sentence into a list of tokens.""" 49 | words = [] 50 | #print(sentence) 51 | for space_separated_fragment in jieba.cut(sentence.strip()): 52 | if isinstance(space_separated_fragment, str): 53 | word = str.encode(space_separated_fragment) 54 | else: 55 | word = space_separated_fragment 56 | words.append(word) 57 | return words 58 | 59 | 60 | def create_vocabulary(vocabulary_path, data_path, max_vocabulary_size, 61 | tokenizer=None, normalize_digits=False): 62 | 63 | if not gfile.Exists(vocabulary_path): 64 | print("Creating vocabulary %s from %s" % (vocabulary_path, data_path)) 65 | vocab = {} 66 | with gfile.GFile(data_path, mode="rb") as f: 67 | counter = 0 68 | for line in f: 69 | counter += 1 70 | if counter % 100 == 0: 71 | print(" processing line %d" % counter) 72 | tokens = tokenizer(line) if tokenizer else basic_tokenizer(line) 73 | for w in tokens: 74 | word = re.sub(_DIGIT_RE, b"0", w) if normalize_digits else w 75 | if word in vocab: 76 | vocab[word] += 1 77 | else: 78 | vocab[word] = 1 79 | vocab_list = _START_VOCAB + sorted(vocab, key=vocab.get, reverse=True) 80 | print('>> Full Vocabulary Size :',len(vocab_list)) 81 | if len(vocab_list) > max_vocabulary_size: 82 | vocab_list = vocab_list[:max_vocabulary_size] 83 | with gfile.GFile(vocabulary_path, mode="wb") as vocab_file: 84 | for w in vocab_list: 85 | vocab_file.write(w + b"\n") 86 | 87 | 88 | def initialize_vocabulary(vocabulary_path): 89 | 90 | if gfile.Exists(vocabulary_path): 91 | rev_vocab = [] 92 | with gfile.GFile(vocabulary_path, mode="rb") as f: 93 | rev_vocab.extend(f.readlines()) 94 | rev_vocab = [line.strip() for line in rev_vocab] 95 | vocab = dict([(x, y) for (y, x) in enumerate(rev_vocab)]) 96 | #ct = 0 97 | #for kk in vocab.keys(): 98 | # print(kk) 99 | # ct += 1 100 | # if ct == 5: 101 | # break 102 | return vocab, rev_vocab 103 | else: 104 | raise ValueError("Vocabulary file %s not found.", vocabulary_path) 105 | 106 | 107 | def sentence_to_token_ids(sentence, vocabulary, tokenizer=None, normalize_digits=False): 108 | 109 | if tokenizer: 110 | words = tokenizer(sentence) 111 | else: 112 | words = basic_tokenizer(sentence) 113 | #print(words[0].decode("utf8")) 114 | #print(words[1]) 115 | if not normalize_digits: 116 | return [vocabulary.get(w.decode("utf8"), UNK_ID) for w in words] 117 | # Normalize digits by 0 before looking words up in the vocabulary. 118 | return [vocabulary.get(re.sub(_DIGIT_RE, b"0", w), UNK_ID) for w in words] 119 | 120 | 121 | def data_to_token_ids(data_path, target_path, vocabulary_path, 122 | tokenizer=None, normalize_digits=False): 123 | 124 | if not gfile.Exists(target_path): 125 | print("Tokenizing data in %s" % data_path) 126 | vocab, _ = initialize_vocabulary(vocabulary_path) 127 | with gfile.GFile(data_path, mode="rb") as data_file: 128 | with gfile.GFile(target_path, mode="w") as tokens_file: 129 | counter = 0 130 | for line in data_file: 131 | counter += 1 132 | if counter % 100000 == 0: 133 | print(" tokenizing line %d" % counter) 134 | token_ids = sentence_to_token_ids(line, vocab, tokenizer, 135 | normalize_digits) 136 | tokens_file.write(" ".join([str(tok) for tok in token_ids]) + "\n") 137 | 138 | 139 | 140 | def prepare_custom_data(working_directory, train_enc, train_dec, test_enc, test_dec, enc_vocabulary_size, dec_vocabulary_size, tokenizer=None): 141 | 142 | # Create vocabularies of the appropriate sizes. 143 | enc_vocab_path = os.path.join(working_directory, "vocab%d.enc" % enc_vocabulary_size) 144 | dec_vocab_path = os.path.join(working_directory, "vocab%d.dec" % dec_vocabulary_size) 145 | create_vocabulary(enc_vocab_path, train_enc, enc_vocabulary_size, tokenizer) 146 | create_vocabulary(dec_vocab_path, train_dec, dec_vocabulary_size, tokenizer) 147 | 148 | # Create token ids for the training data. 149 | enc_train_ids_path = train_enc + (".ids%d" % enc_vocabulary_size) 150 | dec_train_ids_path = train_dec + (".ids%d" % dec_vocabulary_size) 151 | data_to_token_ids(train_enc, enc_train_ids_path, enc_vocab_path, tokenizer) 152 | data_to_token_ids(train_dec, dec_train_ids_path, dec_vocab_path, tokenizer) 153 | 154 | # Create token ids for the development data. 155 | enc_dev_ids_path = test_enc + (".ids%d" % enc_vocabulary_size) 156 | dec_dev_ids_path = test_dec + (".ids%d" % dec_vocabulary_size) 157 | data_to_token_ids(test_enc, enc_dev_ids_path, enc_vocab_path, tokenizer) 158 | data_to_token_ids(test_dec, dec_dev_ids_path, dec_vocab_path, tokenizer) 159 | 160 | return (enc_train_ids_path, dec_train_ids_path, enc_dev_ids_path, dec_dev_ids_path, enc_vocab_path, dec_vocab_path) 161 | -------------------------------------------------------------------------------- /env_setup.sh: -------------------------------------------------------------------------------- 1 | echo 'export PYTHONPATH=$PYTHONPATH:$PWD' >> ~/.bashrc 2 | -------------------------------------------------------------------------------- /execute.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 The TensorFlow Authors. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # ============================================================================== 15 | 16 | from __future__ import absolute_import 17 | from __future__ import division 18 | from __future__ import print_function 19 | 20 | import math 21 | import os 22 | import random 23 | import sys 24 | import time 25 | 26 | import numpy as np 27 | from six.moves import xrange # pylint: disable=redefined-builtin 28 | import tensorflow as tf 29 | 30 | import data_utils 31 | import seq2seq_model 32 | 33 | try: 34 | from ConfigParser import SafeConfigParser 35 | except: 36 | from configparser import SafeConfigParser # In Python 3, ConfigParser has been renamed to configparser for PEP 8 compliance. 37 | 38 | gConfig = {} 39 | 40 | def get_config(config_file='seq2seq.ini'): 41 | parser = SafeConfigParser() 42 | parser.read(config_file) 43 | # get the ints, floats and strings 44 | _conf_ints = [ (key, int(value)) for key,value in parser.items('ints') ] 45 | _conf_floats = [ (key, float(value)) for key,value in parser.items('floats') ] 46 | _conf_strings = [ (key, str(value)) for key,value in parser.items('strings') ] 47 | return dict(_conf_ints + _conf_floats + _conf_strings) 48 | 49 | # We use a number of buckets and pad to the closest one for efficiency. 50 | # See seq2seq_model.Seq2SeqModel for details of how they work. 51 | _buckets = [(5, 10), (10, 15), (20, 25), (40, 50), (55,65), (70,80)] 52 | 53 | 54 | def read_data(source_path, target_path, max_size=None): 55 | """Read data from source and target files and put into buckets. 56 | 57 | Args: 58 | source_path: path to the files with token-ids for the source language. 59 | target_path: path to the file with token-ids for the target language; 60 | it must be aligned with the source file: n-th line contains the desired 61 | output for n-th line from the source_path. 62 | max_size: maximum number of lines to read, all other will be ignored; 63 | if 0 or None, data files will be read completely (no limit). 64 | 65 | Returns: 66 | data_set: a list of length len(_buckets); data_set[n] contains a list of 67 | (source, target) pairs read from the provided data files that fit 68 | into the n-th bucket, i.e., such that len(source) < _buckets[n][0] and 69 | len(target) < _buckets[n][1]; source and target are lists of token-ids. 70 | """ 71 | data_set = [[] for _ in _buckets] 72 | with tf.gfile.GFile(source_path, mode="r") as source_file: 73 | with tf.gfile.GFile(target_path, mode="r") as target_file: 74 | source, target = source_file.readline(), target_file.readline() 75 | counter = 0 76 | while source and target and (not max_size or counter < max_size): 77 | counter += 1 78 | if counter % 100000 == 0: 79 | print(" reading data line %d" % counter) 80 | sys.stdout.flush() 81 | source_ids = [int(x) for x in source.split()] 82 | target_ids = [int(x) for x in target.split()] 83 | target_ids.append(data_utils.EOS_ID) 84 | for bucket_id, (source_size, target_size) in enumerate(_buckets): 85 | if len(source_ids) < source_size and len(target_ids) < target_size: 86 | data_set[bucket_id].append([source_ids, target_ids]) 87 | break 88 | source, target = source_file.readline(), target_file.readline() 89 | return data_set 90 | 91 | 92 | def create_model(session, forward_only): 93 | 94 | """Create model and initialize or load parameters""" 95 | model = seq2seq_model.Seq2SeqModel( gConfig['enc_vocab_size'], gConfig['dec_vocab_size'], _buckets, gConfig['layer_size'], gConfig['num_layers'], gConfig['max_gradient_norm'], gConfig['batch_size'], gConfig['learning_rate'], gConfig['learning_rate_decay_factor'], forward_only=forward_only) 96 | 97 | if 'pretrained_model' in gConfig: 98 | model.saver.restore(session,gConfig['pretrained_model']) 99 | return model 100 | 101 | ckpt = tf.train.get_checkpoint_state(gConfig['working_directory']) 102 | if ckpt and ckpt.model_checkpoint_path: 103 | print("Reading model parameters from %s" % ckpt.model_checkpoint_path) 104 | model.saver.restore(session, ckpt.model_checkpoint_path) 105 | else: 106 | print("Created model with fresh parameters.") 107 | session.run(tf.global_variables_initializer()) 108 | return model 109 | 110 | 111 | def train(): 112 | # prepare dataset 113 | print("Preparing data in %s" % gConfig['working_directory']) 114 | enc_train, dec_train, enc_dev, dec_dev, _, _ = data_utils.prepare_custom_data(gConfig['working_directory'],gConfig['train_enc'],gConfig['train_dec'],gConfig['test_enc'],gConfig['test_dec'],gConfig['enc_vocab_size'],gConfig['dec_vocab_size']) 115 | #raise ValueError("SS") 116 | # setup config to use BFC allocator 117 | config = tf.ConfigProto() 118 | config.gpu_options.allocator_type = 'BFC' 119 | 120 | with tf.Session(config=config) as sess: 121 | # Create model. 122 | print("Creating %d layers of %d units." % (gConfig['num_layers'], gConfig['layer_size'])) 123 | model = create_model(sess, False) 124 | 125 | # Read data into buckets and compute their sizes. 126 | print ("Reading development and training data (limit: %d)." 127 | % gConfig['max_train_data_size']) 128 | dev_set = read_data(enc_dev, dec_dev) 129 | train_set = read_data(enc_train, dec_train, gConfig['max_train_data_size']) 130 | train_bucket_sizes = [len(train_set[b]) for b in xrange(len(_buckets))] 131 | train_total_size = float(sum(train_bucket_sizes)) 132 | 133 | # A bucket scale is a list of increasing numbers from 0 to 1 that we'll use 134 | # to select a bucket. Length of [scale[i], scale[i+1]] is proportional to 135 | # the size if i-th training bucket, as used later. 136 | train_buckets_scale = [sum(train_bucket_sizes[:i + 1]) / train_total_size 137 | for i in xrange(len(train_bucket_sizes))] 138 | 139 | # This is the training loop. 140 | step_time, loss = 0.0, 0.0 141 | current_step = 0 142 | previous_losses = [] 143 | while True: 144 | # Choose a bucket according to data distribution. We pick a random number 145 | # in [0, 1] and use the corresponding interval in train_buckets_scale. 146 | random_number_01 = np.random.random_sample() 147 | bucket_id = min([i for i in xrange(len(train_buckets_scale)) 148 | if train_buckets_scale[i] > random_number_01]) 149 | 150 | # Get a batch and make a step. 151 | start_time = time.time() 152 | encoder_inputs, decoder_inputs, target_weights = model.get_batch( 153 | train_set, bucket_id) 154 | _, step_loss, _ = model.step(sess, encoder_inputs, decoder_inputs, 155 | target_weights, bucket_id, False) 156 | step_time += (time.time() - start_time) / gConfig['steps_per_checkpoint'] 157 | loss += step_loss / gConfig['steps_per_checkpoint'] 158 | current_step += 1 159 | if current_step > 900000: 160 | break 161 | # Once in a while, we save checkpoint, print statistics, and run evals. 162 | if current_step % gConfig['steps_per_checkpoint'] == 0: 163 | # Print statistics for the previous epoch. 164 | perplexity = math.exp(loss) if loss < 300 else float('inf') 165 | print ("global step %d learning rate %.4f step-time %.2f perplexity " 166 | "%.2f" % (model.global_step.eval(), model.learning_rate.eval(), 167 | step_time, perplexity)) 168 | # Decrease learning rate if no improvement was seen over last 3 times. 169 | if len(previous_losses) > 2 and loss > max(previous_losses[-3:]): 170 | sess.run(model.learning_rate_decay_op) 171 | previous_losses.append(loss) 172 | # Save checkpoint and zero timer and loss. 173 | checkpoint_path = os.path.join(gConfig['working_directory'], "seq2seq.ckpt") 174 | model.saver.save(sess, checkpoint_path, global_step=model.global_step) 175 | step_time, loss = 0.0, 0.0 176 | # Run evals on development set and print their perplexity. 177 | for bucket_id in xrange(len(_buckets)): 178 | if len(dev_set[bucket_id]) == 0: 179 | print(" eval: empty bucket %d" % (bucket_id)) 180 | continue 181 | encoder_inputs, decoder_inputs, target_weights = model.get_batch( 182 | dev_set, bucket_id) 183 | _, eval_loss, _ = model.step(sess, encoder_inputs, decoder_inputs, 184 | target_weights, bucket_id, True) 185 | eval_ppx = math.exp(eval_loss) if eval_loss < 300 else float('inf') 186 | print(" eval: bucket %d perplexity %.2f" % (bucket_id, eval_ppx)) 187 | sys.stdout.flush() 188 | 189 | 190 | def decode(): 191 | with tf.Session() as sess: 192 | # Create model and load parameters. 193 | model = create_model(sess, True) 194 | model.batch_size = 1 # We decode one sentence at a time. 195 | 196 | # Load vocabularies. 197 | enc_vocab_path = os.path.join(gConfig['working_directory'],"vocab%d.enc" % gConfig['enc_vocab_size']) 198 | dec_vocab_path = os.path.join(gConfig['working_directory'],"vocab%d.dec" % gConfig['dec_vocab_size']) 199 | 200 | enc_vocab, _ = data_utils.initialize_vocabulary(enc_vocab_path) 201 | _, rev_dec_vocab = data_utils.initialize_vocabulary(dec_vocab_path) 202 | 203 | # Decode from standard input. 204 | sys.stdout.write("> ") 205 | sys.stdout.flush() 206 | sentence = sys.stdin.readline() 207 | while sentence: 208 | # Get token-ids for the input sentence. 209 | token_ids = data_utils.sentence_to_token_ids(tf.compat.as_bytes(sentence), enc_vocab) 210 | # Which bucket does it belong to? 211 | bucket_id = min([b for b in xrange(len(_buckets)) 212 | if _buckets[b][0] > len(token_ids)]) 213 | # Get a 1-element batch to feed the sentence to the model. 214 | encoder_inputs, decoder_inputs, target_weights = model.get_batch( 215 | {bucket_id: [(token_ids, [])]}, bucket_id) 216 | # Get output logits for the sentence. 217 | _, _, output_logits = model.step(sess, encoder_inputs, decoder_inputs, 218 | target_weights, bucket_id, True) 219 | # This is a greedy decoder - outputs are just argmaxes of output_logits. 220 | outputs = [int(np.argmax(logit, axis=1)) for logit in output_logits] 221 | # If there is an EOS symbol in outputs, cut them at that point. 222 | if data_utils.EOS_ID in outputs: 223 | outputs = outputs[:outputs.index(data_utils.EOS_ID)] 224 | # Print out French sentence corresponding to outputs. 225 | print(" ".join([tf.compat.as_str(rev_dec_vocab[output]) for output in outputs])) 226 | print("> ", end="") 227 | sys.stdout.flush() 228 | sentence = sys.stdin.readline() 229 | 230 | 231 | def self_test(): 232 | """Test the translation model.""" 233 | with tf.Session() as sess: 234 | print("Self-test for neural translation model.") 235 | # Create model with vocabularies of 10, 2 small buckets, 2 layers of 32. 236 | model = seq2seq_model.Seq2SeqModel(10, 10, [(3, 3), (6, 6)], 32, 2, 237 | 5.0, 32, 0.3, 0.99, num_samples=8) 238 | sess.run(tf.initialize_all_variables()) 239 | 240 | # Fake data set for both the (3, 3) and (6, 6) bucket. 241 | data_set = ([([1, 1], [2, 2]), ([3, 3], [4]), ([5], [6])], 242 | [([1, 1, 1, 1, 1], [2, 2, 2, 2, 2]), ([3, 3, 3], [5, 6])]) 243 | for _ in xrange(5): # Train the fake model for 5 steps. 244 | bucket_id = random.choice([0, 1]) 245 | encoder_inputs, decoder_inputs, target_weights = model.get_batch( 246 | data_set, bucket_id) 247 | model.step(sess, encoder_inputs, decoder_inputs, target_weights, 248 | bucket_id, False) 249 | 250 | 251 | def init_session(sess, conf='seq2seq.ini'): 252 | global gConfig 253 | gConfig = get_config(conf) 254 | print(gConfig) 255 | # Create model and load parameters. 256 | model = create_model(sess, True) 257 | model.batch_size = 1 # We decode one sentence at a time. 258 | 259 | # Load vocabularies. 260 | enc_vocab_path = os.path.join(gConfig['working_directory'],"vocab%d.enc" % gConfig['enc_vocab_size']) 261 | dec_vocab_path = os.path.join(gConfig['working_directory'],"vocab%d.dec" % gConfig['dec_vocab_size']) 262 | 263 | enc_vocab, _ = data_utils.initialize_vocabulary(enc_vocab_path) 264 | _, rev_dec_vocab = data_utils.initialize_vocabulary(dec_vocab_path) 265 | 266 | return sess, model, enc_vocab, rev_dec_vocab 267 | 268 | def decode_line(sess, model, enc_vocab, rev_dec_vocab, sentence): 269 | # Get token-ids for the input sentence. 270 | #print(sentence) 271 | #token_ids = data_utils.sentence_to_token_ids(tf.compat.as_bytes(sentence), enc_vocab) 272 | token_ids = data_utils.sentence_to_token_ids(sentence, enc_vocab) 273 | print(token_ids) 274 | # Which bucket does it belong to? 275 | bucket_id = min([b for b in xrange(len(_buckets)) if _buckets[b][0] > len(token_ids)]) 276 | 277 | # Get a 1-element batch to feed the sentence to the model. 278 | encoder_inputs, decoder_inputs, target_weights = model.get_batch({bucket_id: [(token_ids, [])]}, bucket_id) 279 | 280 | 281 | _, _, output_logits = model.step(sess, encoder_inputs, decoder_inputs, target_weights, bucket_id, True) 282 | 283 | # This is a greedy decoder - outputs are just argmaxes of output_logits. 284 | 285 | outputs = [int(np.argmax(logit, axis=1)) for logit in output_logits] 286 | 287 | # If there is an EOS symbol in outputs, cut them at that point. 288 | if data_utils.EOS_ID in outputs: 289 | outputs = outputs[:outputs.index(data_utils.EOS_ID)] 290 | 291 | 292 | return "".join([tf.compat.as_str(rev_dec_vocab[output]) for output in outputs]) 293 | 294 | 295 | if __name__ == '__main__': 296 | if len(sys.argv) - 1: 297 | gConfig = get_config(sys.argv[1]) 298 | else: 299 | # get configuration from seq2seq.ini 300 | gConfig = get_config() 301 | 302 | print('\n>> Mode : %s\n' %(gConfig['mode'])) 303 | 304 | if gConfig['mode'] == 'train': 305 | # start training 306 | train() 307 | elif gConfig['mode'] == 'test': 308 | # interactive decode 309 | decode() 310 | else: 311 | # wrong way to execute "serve" 312 | # Use : >> python ui/app.py 313 | # uses seq2seq_serve.ini as conf file 314 | print('Serve Usage : >> python ui/app.py') 315 | print('# uses seq2seq_serve.ini as conf file') 316 | -------------------------------------------------------------------------------- /neuralconvo.ini: -------------------------------------------------------------------------------- 1 | [strings] 2 | # Mode : train, test, serve 3 | mode = train 4 | train_enc = data/train.enc 5 | train_dec = data/train.dec 6 | test_enc = data/test.enc 7 | test_dec = data/test.enc 8 | # folder where checkpoints, vocabulary, temporary data will be stored 9 | working_directory = working_dir/ 10 | [ints] 11 | # vocabulary size 12 | # 20,000 is a reasonable size 13 | enc_vocab_size = 45000 14 | dec_vocab_size = 45000 15 | # number of LSTM layers : 1/2/3 16 | num_layers = 2 17 | # typical options : 128, 256, 512, 1024 18 | layer_size = 900 19 | # dataset size limit; typically none : no limit 20 | max_train_data_size = 50000 21 | batch_size = 10 22 | # steps per checkpoint 23 | # Note : At a checkpoint, models parameters are saved, model is evaluated 24 | # and results are printed 25 | steps_per_checkpoint = 200 26 | [floats] 27 | learning_rate = 0.001 28 | learning_rate_decay_factor = 0.9 29 | max_gradient_norm = 5.0 30 | ############################################################################## 31 | # Note : Edit the bucket sizes at line47 of execute.py (_buckets) 32 | # 33 | # Learn more about the configurations from this link 34 | # https://www.tensorflow.org/versions/r0.9/tutorials/seq2seq/index.html 35 | ############################################################################## 36 | -------------------------------------------------------------------------------- /output_example/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billycyy/chinese_song_generation/6f4c290f0dc6a6d8410084e358c6c64d5c08bdd5/output_example/1.png -------------------------------------------------------------------------------- /output_example/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billycyy/chinese_song_generation/6f4c290f0dc6a6d8410084e358c6c64d5c08bdd5/output_example/2.png -------------------------------------------------------------------------------- /output_example/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billycyy/chinese_song_generation/6f4c290f0dc6a6d8410084e358c6c64d5c08bdd5/output_example/3.png -------------------------------------------------------------------------------- /output_example/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billycyy/chinese_song_generation/6f4c290f0dc6a6d8410084e358c6c64d5c08bdd5/output_example/4.png -------------------------------------------------------------------------------- /seq2seq.ini: -------------------------------------------------------------------------------- 1 | [strings] 2 | # Mode : train, test, serve 3 | mode = train 4 | train_enc = data/train.enc 5 | train_dec = data/train.dec 6 | test_enc = data/test.enc 7 | test_dec = data/test.dec 8 | # folder where checkpoints, vocabulary, temporary data will be stored 9 | working_directory = working_dir/ 10 | [ints] 11 | # vocabulary size 12 | # 20,000 is a reasonable size 13 | enc_vocab_size = 50000 14 | dec_vocab_size = 50000 15 | # number of LSTM layers : 1/2/3 16 | num_layers = 2 17 | # typical options : 128, 256, 512, 1024 18 | layer_size = 256 19 | # dataset size limit; typically none : no limit 20 | max_train_data_size = 0 21 | batch_size = 64 22 | # steps per checkpoint 23 | # Note : At a checkpoint, models parameters are saved, model is evaluated 24 | # and results are printed 25 | steps_per_checkpoint = 20000 26 | [floats] 27 | learning_rate = 0.5 28 | learning_rate_decay_factor = 0.99 29 | max_gradient_norm = 5.0 30 | ############################################################################## 31 | # Note : Edit the bucket sizes at line47 of execute.py (_buckets) 32 | # 33 | # Learn more about the configurations from this link 34 | # https://www.tensorflow.org/versions/r0.9/tutorials/seq2seq/index.html 35 | ############################################################################## 36 | -------------------------------------------------------------------------------- /seq2seq_model.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 The TensorFlow Authors. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # ============================================================================== 15 | 16 | """Sequence-to-sequence model with an attention mechanism.""" 17 | 18 | from __future__ import absolute_import 19 | from __future__ import division 20 | from __future__ import print_function 21 | 22 | import random 23 | 24 | import numpy as np 25 | from six.moves import xrange # pylint: disable=redefined-builtin 26 | import tensorflow as tf 27 | 28 | from tensorflow.models.rnn.translate import data_utils 29 | 30 | 31 | class Seq2SeqModel(object): 32 | """Sequence-to-sequence model with attention and for multiple buckets. 33 | 34 | This class implements a multi-layer recurrent neural network as encoder, 35 | and an attention-based decoder. This is the same as the model described in 36 | this paper: http://arxiv.org/abs/1412.7449 - please look there for details, 37 | or into the seq2seq library for complete model implementation. 38 | This class also allows to use GRU cells in addition to LSTM cells, and 39 | sampled softmax to handle large output vocabulary size. A single-layer 40 | version of this model, but with bi-directional encoder, was presented in 41 | http://arxiv.org/abs/1409.0473 42 | and sampled softmax is described in Section 3 of the following paper. 43 | http://arxiv.org/abs/1412.2007 44 | """ 45 | 46 | def __init__(self, source_vocab_size, target_vocab_size, buckets, size, 47 | num_layers, max_gradient_norm, batch_size, learning_rate, 48 | learning_rate_decay_factor, use_lstm=False, 49 | num_samples=512, forward_only=False): 50 | """Create the model. 51 | 52 | Args: 53 | source_vocab_size: size of the source vocabulary. 54 | target_vocab_size: size of the target vocabulary. 55 | buckets: a list of pairs (I, O), where I specifies maximum input length 56 | that will be processed in that bucket, and O specifies maximum output 57 | length. Training instances that have inputs longer than I or outputs 58 | longer than O will be pushed to the next bucket and padded accordingly. 59 | We assume that the list is sorted, e.g., [(2, 4), (8, 16)]. 60 | size: number of units in each layer of the model. 61 | num_layers: number of layers in the model. 62 | max_gradient_norm: gradients will be clipped to maximally this norm. 63 | batch_size: the size of the batches used during training; 64 | the model construction is independent of batch_size, so it can be 65 | changed after initialization if this is convenient, e.g., for decoding. 66 | learning_rate: learning rate to start with. 67 | learning_rate_decay_factor: decay learning rate by this much when needed. 68 | use_lstm: if true, we use LSTM cells instead of GRU cells. 69 | num_samples: number of samples for sampled softmax. 70 | forward_only: if set, we do not construct the backward pass in the model. 71 | """ 72 | self.source_vocab_size = source_vocab_size 73 | self.target_vocab_size = target_vocab_size 74 | self.buckets = buckets 75 | self.batch_size = batch_size 76 | self.learning_rate = tf.Variable(float(learning_rate), trainable=False) 77 | self.learning_rate_decay_op = self.learning_rate.assign( 78 | self.learning_rate * learning_rate_decay_factor) 79 | self.global_step = tf.Variable(0, trainable=False) 80 | 81 | # If we use sampled softmax, we need an output projection. 82 | output_projection = None 83 | softmax_loss_function = None 84 | # Sampled softmax only makes sense if we sample less than vocabulary size. 85 | if num_samples > 0 and num_samples < self.target_vocab_size: 86 | w = tf.get_variable("proj_w", [size, self.target_vocab_size]) 87 | w_t = tf.transpose(w) 88 | b = tf.get_variable("proj_b", [self.target_vocab_size]) 89 | output_projection = (w, b) 90 | 91 | def sampled_loss(inputs, labels): 92 | labels = tf.reshape(labels, [-1, 1]) 93 | return tf.nn.sampled_softmax_loss(w_t, b, inputs, labels, num_samples, 94 | self.target_vocab_size) 95 | softmax_loss_function = sampled_loss 96 | 97 | # Create the internal multi-layer cell for our RNN. 98 | single_cell = tf.nn.rnn_cell.GRUCell(size) 99 | if use_lstm: 100 | single_cell = tf.nn.rnn_cell.BasicLSTMCell(size) 101 | cell = single_cell 102 | if num_layers > 1: 103 | cell = tf.nn.rnn_cell.MultiRNNCell([single_cell] * num_layers) 104 | 105 | # The seq2seq function: we use embedding for the input and attention. 106 | def seq2seq_f(encoder_inputs, decoder_inputs, do_decode): 107 | return tf.nn.seq2seq.embedding_attention_seq2seq( 108 | encoder_inputs, decoder_inputs, cell, 109 | num_encoder_symbols=source_vocab_size, 110 | num_decoder_symbols=target_vocab_size, 111 | embedding_size=size, 112 | output_projection=output_projection, 113 | feed_previous=do_decode) 114 | # Feeds for inputs. 115 | self.encoder_inputs = [] 116 | self.decoder_inputs = [] 117 | self.target_weights = [] 118 | for i in xrange(buckets[-1][0]): # Last bucket is the biggest one. 119 | self.encoder_inputs.append(tf.placeholder(tf.int32, shape=[None], 120 | name="encoder{0}".format(i))) 121 | for i in xrange(buckets[-1][1] + 1): 122 | self.decoder_inputs.append(tf.placeholder(tf.int32, shape=[None], 123 | name="decoder{0}".format(i))) 124 | self.target_weights.append(tf.placeholder(tf.float32, shape=[None], 125 | name="weight{0}".format(i))) 126 | 127 | # Our targets are decoder inputs shifted by one. 128 | targets = [self.decoder_inputs[i + 1] 129 | for i in xrange(len(self.decoder_inputs) - 1)] 130 | 131 | # Training outputs and losses. 132 | if forward_only: 133 | self.outputs, self.losses = tf.nn.seq2seq.model_with_buckets( 134 | self.encoder_inputs, self.decoder_inputs, targets, 135 | self.target_weights, buckets, lambda x, y: seq2seq_f(x, y, True), 136 | softmax_loss_function=softmax_loss_function) 137 | # If we use output projection, we need to project outputs for decoding. 138 | if output_projection is not None: 139 | for b in xrange(len(buckets)): 140 | self.outputs[b] = [ 141 | tf.matmul(output, output_projection[0]) + output_projection[1] 142 | for output in self.outputs[b] 143 | ] 144 | else: 145 | self.outputs, self.losses = tf.nn.seq2seq.model_with_buckets( 146 | self.encoder_inputs, self.decoder_inputs, targets, 147 | self.target_weights, buckets, 148 | lambda x, y: seq2seq_f(x, y, False), 149 | softmax_loss_function=softmax_loss_function) 150 | 151 | # Gradients and SGD update operation for training the model. 152 | params = tf.trainable_variables() 153 | if not forward_only: 154 | self.gradient_norms = [] 155 | self.updates = [] 156 | opt = tf.train.GradientDescentOptimizer(self.learning_rate) 157 | for b in xrange(len(buckets)): 158 | gradients = tf.gradients(self.losses[b], params) 159 | clipped_gradients, norm = tf.clip_by_global_norm(gradients, 160 | max_gradient_norm) 161 | self.gradient_norms.append(norm) 162 | self.updates.append(opt.apply_gradients( 163 | zip(clipped_gradients, params), global_step=self.global_step)) 164 | 165 | self.saver = tf.train.Saver(var_list=tf.global_variables(),max_to_keep=0) 166 | 167 | def step(self, session, encoder_inputs, decoder_inputs, target_weights, 168 | bucket_id, forward_only): 169 | """Run a step of the model feeding the given inputs. 170 | 171 | Args: 172 | session: tensorflow session to use. 173 | encoder_inputs: list of numpy int vectors to feed as encoder inputs. 174 | decoder_inputs: list of numpy int vectors to feed as decoder inputs. 175 | target_weights: list of numpy float vectors to feed as target weights. 176 | bucket_id: which bucket of the model to use. 177 | forward_only: whether to do the backward step or only forward. 178 | 179 | Returns: 180 | A triple consisting of gradient norm (or None if we did not do backward), 181 | average perplexity, and the outputs. 182 | 183 | Raises: 184 | ValueError: if length of encoder_inputs, decoder_inputs, or 185 | target_weights disagrees with bucket size for the specified bucket_id. 186 | """ 187 | # Check if the sizes match. 188 | encoder_size, decoder_size = self.buckets[bucket_id] 189 | if len(encoder_inputs) != encoder_size: 190 | raise ValueError("Encoder length must be equal to the one in bucket," 191 | " %d != %d." % (len(encoder_inputs), encoder_size)) 192 | if len(decoder_inputs) != decoder_size: 193 | raise ValueError("Decoder length must be equal to the one in bucket," 194 | " %d != %d." % (len(decoder_inputs), decoder_size)) 195 | if len(target_weights) != decoder_size: 196 | raise ValueError("Weights length must be equal to the one in bucket," 197 | " %d != %d." % (len(target_weights), decoder_size)) 198 | 199 | # Input feed: encoder inputs, decoder inputs, target_weights, as provided. 200 | input_feed = {} 201 | for l in xrange(encoder_size): 202 | input_feed[self.encoder_inputs[l].name] = encoder_inputs[l] 203 | for l in xrange(decoder_size): 204 | input_feed[self.decoder_inputs[l].name] = decoder_inputs[l] 205 | input_feed[self.target_weights[l].name] = target_weights[l] 206 | 207 | # Since our targets are decoder inputs shifted by one, we need one more. 208 | last_target = self.decoder_inputs[decoder_size].name 209 | input_feed[last_target] = np.zeros([self.batch_size], dtype=np.int32) 210 | 211 | # Output feed: depends on whether we do a backward step or not. 212 | if not forward_only: 213 | output_feed = [self.updates[bucket_id], # Update Op that does SGD. 214 | self.gradient_norms[bucket_id], # Gradient norm. 215 | self.losses[bucket_id]] # Loss for this batch. 216 | else: 217 | output_feed = [self.losses[bucket_id]] # Loss for this batch. 218 | for l in xrange(decoder_size): # Output logits. 219 | output_feed.append(self.outputs[bucket_id][l]) 220 | #print(output_feed) 221 | #print(input_feed) 222 | outputs = session.run(output_feed, input_feed) 223 | if not forward_only: 224 | return outputs[1], outputs[2], None # Gradient norm, loss, no outputs. 225 | else: 226 | return None, outputs[0], outputs[1:] # No gradient norm, loss, outputs. 227 | 228 | def get_batch(self, data, bucket_id): 229 | """Get a random batch of data from the specified bucket, prepare for step. 230 | 231 | To feed data in step(..) it must be a list of batch-major vectors, while 232 | data here contains single length-major cases. So the main logic of this 233 | function is to re-index data cases to be in the proper format for feeding. 234 | 235 | Args: 236 | data: a tuple of size len(self.buckets) in which each element contains 237 | lists of pairs of input and output data that we use to create a batch. 238 | bucket_id: integer, which bucket to get the batch for. 239 | 240 | Returns: 241 | The triple (encoder_inputs, decoder_inputs, target_weights) for 242 | the constructed batch that has the proper format to call step(...) later. 243 | """ 244 | encoder_size, decoder_size = self.buckets[bucket_id] 245 | encoder_inputs, decoder_inputs = [], [] 246 | 247 | # Get a random batch of encoder and decoder inputs from data, 248 | # pad them if needed, reverse encoder inputs and add GO to decoder. 249 | for _ in xrange(self.batch_size): 250 | encoder_input, decoder_input = random.choice(data[bucket_id]) 251 | 252 | # Encoder inputs are padded and then reversed. 253 | encoder_pad = [data_utils.PAD_ID] * (encoder_size - len(encoder_input)) 254 | encoder_inputs.append(list(reversed(encoder_input + encoder_pad))) 255 | 256 | # Decoder inputs get an extra "GO" symbol, and are padded then. 257 | decoder_pad_size = decoder_size - len(decoder_input) - 1 258 | decoder_inputs.append([data_utils.GO_ID] + decoder_input + 259 | [data_utils.PAD_ID] * decoder_pad_size) 260 | 261 | # Now we create batch-major vectors from the data selected above. 262 | batch_encoder_inputs, batch_decoder_inputs, batch_weights = [], [], [] 263 | 264 | # Batch encoder inputs are just re-indexed encoder_inputs. 265 | for length_idx in xrange(encoder_size): 266 | batch_encoder_inputs.append( 267 | np.array([encoder_inputs[batch_idx][length_idx] 268 | for batch_idx in xrange(self.batch_size)], dtype=np.int32)) 269 | 270 | # Batch decoder inputs are re-indexed decoder_inputs, we create weights. 271 | for length_idx in xrange(decoder_size): 272 | batch_decoder_inputs.append( 273 | np.array([decoder_inputs[batch_idx][length_idx] 274 | for batch_idx in xrange(self.batch_size)], dtype=np.int32)) 275 | 276 | # Create target_weights to be 0 for targets that are padding. 277 | batch_weight = np.ones(self.batch_size, dtype=np.float32) 278 | for batch_idx in xrange(self.batch_size): 279 | # We set weight to 0 if the corresponding target is a PAD symbol. 280 | # The corresponding target is decoder_input shifted by 1 forward. 281 | if length_idx < decoder_size - 1: 282 | target = decoder_inputs[batch_idx][length_idx + 1] 283 | if length_idx == decoder_size - 1 or target == data_utils.PAD_ID: 284 | batch_weight[batch_idx] = 0.0 285 | batch_weights.append(batch_weight) 286 | return batch_encoder_inputs, batch_decoder_inputs, batch_weights 287 | -------------------------------------------------------------------------------- /seq2seq_serve.ini: -------------------------------------------------------------------------------- 1 | [strings] 2 | # Mode : train, test, serve 3 | mode = serve 4 | train_enc = data/train.enc 5 | train_dec = data/train.dec 6 | test_enc = data/test.enc 7 | test_dec = data/test.enc 8 | # folder where checkpoints, vocabulary, temporary data will be stored 9 | working_directory = working_dir/ 10 | [ints] 11 | # vocabulary size 12 | # 20,000 is a reasonable size 13 | enc_vocab_size = 50000 14 | dec_vocab_size = 50000 15 | # number of LSTM layers : 1/2/3 16 | num_layers = 2 17 | # typical options : 128, 256, 512, 1024 18 | layer_size = 256 19 | # dataset size limit; typically none : no limit 20 | max_train_data_size = 0 21 | batch_size = 64 22 | # steps per checkpoint 23 | # Note : At a checkpoint, models parameters are saved, model is evaluated 24 | # and results are printed 25 | steps_per_checkpoint = 300 26 | [floats] 27 | learning_rate = 0.5 28 | learning_rate_decay_factor = 0.99 29 | max_gradient_norm = 5.0 30 | ############################################################################## 31 | # Note : Edit the bucket sizes at line47 of execute.py (_buckets) 32 | # 33 | # Learn more about the configurations from this link 34 | # https://www.tensorflow.org/versions/r0.9/tutorials/seq2seq/index.html 35 | ############################################################################## 36 | -------------------------------------------------------------------------------- /ui/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.pyc 3 | *.pyo 4 | env 5 | env* 6 | dist 7 | build 8 | *.egg 9 | *.egg-info 10 | _mailinglist 11 | .tox 12 | .cache/ 13 | .idea/ 14 | -------------------------------------------------------------------------------- /ui/README.md: -------------------------------------------------------------------------------- 1 | # Flask Chatbox 2 | 3 | A simple flask application that provides an UI for chat and handles the model internally. 4 | -------------------------------------------------------------------------------- /ui/__init.py__: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billycyy/chinese_song_generation/6f4c290f0dc6a6d8410084e358c6c64d5c08bdd5/ui/__init.py__ -------------------------------------------------------------------------------- /ui/app.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from flask import Flask, render_template, request 3 | from flask import jsonify 4 | 5 | app = Flask(__name__,static_url_path="/static") 6 | 7 | ############# 8 | # Routing 9 | # 10 | @app.route('/message', methods=['POST']) 11 | def reply(): 12 | print(request.form['msg']) 13 | res = list() 14 | for fir in request.form['msg'].split(","): 15 | res.append(fir) 16 | res_set = set(res) 17 | # remove some duplicated lyrics 18 | for i in range(10): 19 | n = 4 20 | while len(basic_tokenizer(",".join(res[-n:]))) > 65 and n > 1: 21 | n -= 1 22 | cand = execute.decode_line(sess, model, enc_vocab, rev_dec_vocab, u','.join(res[-n:])) 23 | while cand in res_set and n > 1: 24 | n -= 1 25 | cand = execute.decode_line(sess, model, enc_vocab, rev_dec_vocab, u','.join(res[-n:])) 26 | if cand != res[-1]: 27 | while len(res) > 1 and res[-1] == res[-2]: 28 | res.pop() 29 | res.append(cand) 30 | res_set.add(cand) 31 | while len(res) > 1 and res[-1] == res[-2]: 32 | res.pop() 33 | return jsonify( { 'text': "\n".join(res) } ) 34 | 35 | @app.route("/") 36 | def index(): 37 | return render_template("index.html") 38 | ############# 39 | 40 | ''' 41 | Init seq2seq model 42 | 43 | 1. Call main from execute.py 44 | 2. Create decode_line function that takes message as input 45 | ''' 46 | #_________________________________________________________________ 47 | import sys 48 | import os.path 49 | 50 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))) 51 | import tensorflow as tf 52 | import execute 53 | from data_utils import basic_tokenizer 54 | 55 | sess = tf.Session() 56 | sess, model, enc_vocab, rev_dec_vocab = execute.init_session(sess, conf='seq2seq_serve.ini') 57 | #_________________________________________________________________ 58 | 59 | # start app 60 | if (__name__ == "__main__"): 61 | app.run(port = 5000) 62 | -------------------------------------------------------------------------------- /ui/requirements.txt: -------------------------------------------------------------------------------- 1 | click==6.6 2 | Flask==0.11.1 3 | itsdangerous==0.24 4 | Jinja2==2.8 5 | MarkupSafe==0.23 6 | Werkzeug==0.11.10 7 | -------------------------------------------------------------------------------- /ui/seq2seq_serve.ini: -------------------------------------------------------------------------------- 1 | [strings] 2 | # Mode : train, test, serve 3 | mode = serve 4 | train_enc = data/train.enc 5 | train_dec = data/train.dec 6 | test_enc = data/test.enc 7 | test_dec = data/test.enc 8 | # folder where checkpoints, vocabulary, temporary data will be stored 9 | working_directory = working_dir/ 10 | # pretrained_model = seq2seq.ckpt-1260 11 | [ints] 12 | # vocabulary size 13 | # 20,000 is a reasonable size 14 | enc_vocab_size = 50000 15 | dec_vocab_size = 50000 16 | # number of LSTM layers : 1/2/3 17 | num_layers = 2 18 | # typical options : 128, 256, 512, 1024 19 | layer_size = 256 20 | # dataset size limit; typically none : no limit 21 | max_train_data_size = 0 22 | batch_size = 64 23 | # steps per checkpoint 24 | # Note : At a checkpoint, models parameters are saved, model is evaluated 25 | # and results are printed 26 | steps_per_checkpoint = 20000 27 | [floats] 28 | learning_rate = 0.5 29 | learning_rate_decay_factor = 0.99 30 | max_gradient_norm = 5.0 31 | ############################################################################## 32 | # Note : Edit the bucket sizes at line47 of execute.py (_buckets) 33 | # 34 | # Learn more about the configurations from this link 35 | # https://www.tensorflow.org/versions/r0.9/tutorials/seq2seq/index.html 36 | ############################################################################## 37 | -------------------------------------------------------------------------------- /ui/setup.sh: -------------------------------------------------------------------------------- 1 | sudo -H pip install --upgrade pip 2 | sudo -H pip install -r requirements.txt 3 | -------------------------------------------------------------------------------- /ui/static/css/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v4.0.0 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block}audio:not([controls]){display:none;height:0}progress{vertical-align:baseline}template,[hidden]{display:none}a{background-color:transparent}a:active,a:hover{outline-width:0}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:inherit}b,strong{font-weight:bolder}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}img{border-style:none}svg:not(:root){overflow:hidden}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}figure{margin:1em 40px}hr{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;height:0;overflow:visible}button,input,select,textarea{font:inherit;margin:0}optgroup{font-weight:bold}button,input,select{overflow:visible}button,select{text-transform:none}button,[type="button"],[type="reset"],[type="submit"]{cursor:pointer}[disabled]{cursor:default}button,html [type="button"],[type="reset"],[type="submit"]{-webkit-appearance:button}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button:-moz-focusring,input:-moz-focusring{outline:1px dotted ButtonText}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}textarea{overflow:auto}[type="checkbox"],[type="radio"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}[type="number"]::-webkit-inner-spin-button,[type="number"]::-webkit-outer-spin-button{height:auto}[type="search"]{-webkit-appearance:textfield}[type="search"]::-webkit-search-cancel-button,[type="search"]::-webkit-search-decoration{-webkit-appearance:none} 2 | -------------------------------------------------------------------------------- /ui/static/css/style.css: -------------------------------------------------------------------------------- 1 | /*-------------------- 2 | Mixins 3 | --------------------*/ 4 | /*-------------------- 5 | Body 6 | --------------------*/ 7 | *, 8 | *::before, 9 | *::after { 10 | box-sizing: border-box; 11 | } 12 | 13 | html, 14 | body { 15 | height: 100%; 16 | } 17 | 18 | body { 19 | background: -webkit-linear-gradient(315deg, #044f48, #2a7561); 20 | background: linear-gradient(135deg, #044f48, #2a7561); 21 | background-size: cover; 22 | font-family: 'Open Sans', sans-serif; 23 | font-size: 12px; 24 | line-height: 1.3; 25 | overflow: hidden; 26 | } 27 | 28 | .bg { 29 | width: 100%; 30 | height: 100%; 31 | top: 0; 32 | left: 0; 33 | z-index: 1; 34 | background: url("/static/res/hd2.jpg") no-repeat 0 0; 35 | -webkit-filter: blur(80px); 36 | filter: blur(80px); 37 | -webkit-transform: scale(1.2); 38 | transform: scale(1.2); 39 | } 40 | 41 | /*-------------------- 42 | Chat 43 | --------------------*/ 44 | .chat { 45 | position: absolute; 46 | top: 50%; 47 | left: 50%; 48 | -webkit-transform: translate(-50%, -50%); 49 | transform: translate(-50%, -50%); 50 | width: 300px; 51 | height: 80vh; 52 | max-height: 500px; 53 | z-index: 2; 54 | overflow: hidden; 55 | box-shadow: 0 5px 30px rgba(0, 0, 0, 0.2); 56 | background: rgba(0, 0, 0, 0.5); 57 | border-radius: 20px; 58 | display: -webkit-box; 59 | display: -webkit-flex; 60 | display: -ms-flexbox; 61 | display: flex; 62 | -webkit-box-pack: justify; 63 | -webkit-justify-content: space-between; 64 | -ms-flex-pack: justify; 65 | justify-content: space-between; 66 | -webkit-box-orient: vertical; 67 | -webkit-box-direction: normal; 68 | -webkit-flex-direction: column; 69 | -ms-flex-direction: column; 70 | flex-direction: column; 71 | } 72 | 73 | /*-------------------- 74 | Chat Title 75 | --------------------*/ 76 | .chat-title { 77 | -webkit-box-flex: 0; 78 | -webkit-flex: 0 1 45px; 79 | -ms-flex: 0 1 45px; 80 | flex: 0 1 45px; 81 | position: relative; 82 | z-index: 2; 83 | background: rgba(0, 0, 0, 0.2); 84 | color: #fff; 85 | text-transform: uppercase; 86 | text-align: left; 87 | padding: 10px 10px 10px 50px; 88 | } 89 | .chat-title h1, .chat-title h2 { 90 | font-weight: normal; 91 | font-size: 10px; 92 | margin: 0; 93 | padding: 0; 94 | } 95 | .chat-title h2 { 96 | color: rgba(255, 255, 255, 0.5); 97 | font-size: 8px; 98 | letter-spacing: 1px; 99 | } 100 | .chat-title .avatar { 101 | position: absolute; 102 | z-index: 1; 103 | top: 8px; 104 | left: 9px; 105 | border-radius: 30px; 106 | width: 30px; 107 | height: 30px; 108 | overflow: hidden; 109 | margin: 0; 110 | padding: 0; 111 | border: 2px solid rgba(255, 255, 255, 0.24); 112 | } 113 | .chat-title .avatar img { 114 | width: 100%; 115 | height: auto; 116 | } 117 | 118 | /*-------------------- 119 | Messages 120 | --------------------*/ 121 | .messages { 122 | -webkit-box-flex: 1; 123 | -webkit-flex: 1 1 auto; 124 | -ms-flex: 1 1 auto; 125 | flex: 1 1 auto; 126 | color: rgba(255, 255, 255, 0.5); 127 | overflow: hidden; 128 | position: relative; 129 | width: 100%; 130 | } 131 | .messages .messages-content { 132 | position: absolute; 133 | top: 0; 134 | left: 0; 135 | height: 101%; 136 | width: 100%; 137 | } 138 | .messages .message { 139 | clear: both; 140 | float: left; 141 | padding: 6px 10px 7px; 142 | border-radius: 10px 10px 10px 0; 143 | background: rgba(0, 0, 0, 0.3); 144 | margin: 8px 0; 145 | font-size: 11px; 146 | line-height: 1.4; 147 | margin-left: 35px; 148 | position: relative; 149 | text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); 150 | } 151 | .messages .message .timestamp { 152 | position: absolute; 153 | bottom: -15px; 154 | font-size: 9px; 155 | color: rgba(255, 255, 255, 0.3); 156 | } 157 | .messages .message::before { 158 | content: ''; 159 | position: absolute; 160 | bottom: -6px; 161 | border-top: 6px solid rgba(0, 0, 0, 0.3); 162 | left: 0; 163 | border-right: 7px solid transparent; 164 | } 165 | .messages .message .avatar { 166 | position: absolute; 167 | z-index: 1; 168 | bottom: -15px; 169 | left: -35px; 170 | border-radius: 30px; 171 | width: 30px; 172 | height: 30px; 173 | overflow: hidden; 174 | margin: 0; 175 | padding: 0; 176 | border: 2px solid rgba(255, 255, 255, 0.24); 177 | } 178 | .messages .message .avatar img { 179 | width: 100%; 180 | height: auto; 181 | } 182 | .messages .message.message-personal { 183 | float: right; 184 | color: #fff; 185 | text-align: right; 186 | background: -webkit-linear-gradient(330deg, #248A52, #257287); 187 | background: linear-gradient(120deg, #248A52, #257287); 188 | border-radius: 10px 10px 0 10px; 189 | } 190 | .messages .message.message-personal::before { 191 | left: auto; 192 | right: 0; 193 | border-right: none; 194 | border-left: 5px solid transparent; 195 | border-top: 4px solid #257287; 196 | bottom: -4px; 197 | } 198 | .messages .message:last-child { 199 | margin-bottom: 30px; 200 | } 201 | .messages .message.new { 202 | -webkit-transform: scale(0); 203 | transform: scale(0); 204 | -webkit-transform-origin: 0 0; 205 | transform-origin: 0 0; 206 | -webkit-animation: bounce 500ms linear both; 207 | animation: bounce 500ms linear both; 208 | } 209 | .messages .message.loading::before { 210 | position: absolute; 211 | top: 50%; 212 | left: 50%; 213 | -webkit-transform: translate(-50%, -50%); 214 | transform: translate(-50%, -50%); 215 | content: ''; 216 | display: block; 217 | width: 3px; 218 | height: 3px; 219 | border-radius: 50%; 220 | background: rgba(255, 255, 255, 0.5); 221 | z-index: 2; 222 | margin-top: 4px; 223 | -webkit-animation: ball 0.45s cubic-bezier(0, 0, 0.15, 1) alternate infinite; 224 | animation: ball 0.45s cubic-bezier(0, 0, 0.15, 1) alternate infinite; 225 | border: none; 226 | -webkit-animation-delay: .15s; 227 | animation-delay: .15s; 228 | } 229 | .messages .message.loading span { 230 | display: block; 231 | font-size: 0; 232 | width: 20px; 233 | height: 10px; 234 | position: relative; 235 | } 236 | .messages .message.loading span::before { 237 | position: absolute; 238 | top: 50%; 239 | left: 50%; 240 | -webkit-transform: translate(-50%, -50%); 241 | transform: translate(-50%, -50%); 242 | content: ''; 243 | display: block; 244 | width: 3px; 245 | height: 3px; 246 | border-radius: 50%; 247 | background: rgba(255, 255, 255, 0.5); 248 | z-index: 2; 249 | margin-top: 4px; 250 | -webkit-animation: ball 0.45s cubic-bezier(0, 0, 0.15, 1) alternate infinite; 251 | animation: ball 0.45s cubic-bezier(0, 0, 0.15, 1) alternate infinite; 252 | margin-left: -7px; 253 | } 254 | .messages .message.loading span::after { 255 | position: absolute; 256 | top: 50%; 257 | left: 50%; 258 | -webkit-transform: translate(-50%, -50%); 259 | transform: translate(-50%, -50%); 260 | content: ''; 261 | display: block; 262 | width: 3px; 263 | height: 3px; 264 | border-radius: 50%; 265 | background: rgba(255, 255, 255, 0.5); 266 | z-index: 2; 267 | margin-top: 4px; 268 | -webkit-animation: ball 0.45s cubic-bezier(0, 0, 0.15, 1) alternate infinite; 269 | animation: ball 0.45s cubic-bezier(0, 0, 0.15, 1) alternate infinite; 270 | margin-left: 7px; 271 | -webkit-animation-delay: .3s; 272 | animation-delay: .3s; 273 | } 274 | 275 | /*-------------------- 276 | Message Box 277 | --------------------*/ 278 | .message-box { 279 | -webkit-box-flex: 0; 280 | -webkit-flex: 0 1 40px; 281 | -ms-flex: 0 1 40px; 282 | flex: 0 1 40px; 283 | width: 100%; 284 | background: rgba(0, 0, 0, 0.3); 285 | padding: 10px; 286 | position: relative; 287 | } 288 | .message-box .message-input { 289 | background: none; 290 | border: none; 291 | outline: none !important; 292 | resize: none; 293 | color: rgba(255, 255, 255, 0.7); 294 | font-size: 11px; 295 | height: 17px; 296 | margin: 0; 297 | padding-right: 20px; 298 | width: 265px; 299 | } 300 | .message-box textarea:focus:-webkit-placeholder { 301 | color: transparent; 302 | } 303 | .message-box .message-submit { 304 | position: absolute; 305 | z-index: 1; 306 | top: 9px; 307 | right: 10px; 308 | color: #fff; 309 | border: none; 310 | background: #248A52; 311 | font-size: 10px; 312 | text-transform: uppercase; 313 | line-height: 1; 314 | padding: 6px 10px; 315 | border-radius: 10px; 316 | outline: none !important; 317 | -webkit-transition: background .2s ease; 318 | transition: background .2s ease; 319 | } 320 | .message-box .message-submit:hover { 321 | background: #1D7745; 322 | } 323 | 324 | /*-------------------- 325 | Custom Srollbar 326 | --------------------*/ 327 | .mCSB_scrollTools { 328 | margin: 1px -3px 1px 0; 329 | opacity: 0; 330 | } 331 | 332 | .mCSB_inside > .mCSB_container { 333 | margin-right: 0px; 334 | padding: 0 10px; 335 | } 336 | 337 | .mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar { 338 | background-color: rgba(0, 0, 0, 0.5) !important; 339 | } 340 | 341 | /*-------------------- 342 | Bounce 343 | --------------------*/ 344 | @-webkit-keyframes bounce { 345 | 0% { 346 | -webkit-transform: matrix3d(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 347 | transform: matrix3d(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 348 | } 349 | 4.7% { 350 | -webkit-transform: matrix3d(0.45, 0, 0, 0, 0, 0.45, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 351 | transform: matrix3d(0.45, 0, 0, 0, 0, 0.45, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 352 | } 353 | 9.41% { 354 | -webkit-transform: matrix3d(0.883, 0, 0, 0, 0, 0.883, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 355 | transform: matrix3d(0.883, 0, 0, 0, 0, 0.883, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 356 | } 357 | 14.11% { 358 | -webkit-transform: matrix3d(1.141, 0, 0, 0, 0, 1.141, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 359 | transform: matrix3d(1.141, 0, 0, 0, 0, 1.141, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 360 | } 361 | 18.72% { 362 | -webkit-transform: matrix3d(1.212, 0, 0, 0, 0, 1.212, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 363 | transform: matrix3d(1.212, 0, 0, 0, 0, 1.212, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 364 | } 365 | 24.32% { 366 | -webkit-transform: matrix3d(1.151, 0, 0, 0, 0, 1.151, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 367 | transform: matrix3d(1.151, 0, 0, 0, 0, 1.151, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 368 | } 369 | 29.93% { 370 | -webkit-transform: matrix3d(1.048, 0, 0, 0, 0, 1.048, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 371 | transform: matrix3d(1.048, 0, 0, 0, 0, 1.048, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 372 | } 373 | 35.54% { 374 | -webkit-transform: matrix3d(0.979, 0, 0, 0, 0, 0.979, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 375 | transform: matrix3d(0.979, 0, 0, 0, 0, 0.979, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 376 | } 377 | 41.04% { 378 | -webkit-transform: matrix3d(0.961, 0, 0, 0, 0, 0.961, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 379 | transform: matrix3d(0.961, 0, 0, 0, 0, 0.961, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 380 | } 381 | 52.15% { 382 | -webkit-transform: matrix3d(0.991, 0, 0, 0, 0, 0.991, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 383 | transform: matrix3d(0.991, 0, 0, 0, 0, 0.991, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 384 | } 385 | 63.26% { 386 | -webkit-transform: matrix3d(1.007, 0, 0, 0, 0, 1.007, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 387 | transform: matrix3d(1.007, 0, 0, 0, 0, 1.007, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 388 | } 389 | 85.49% { 390 | -webkit-transform: matrix3d(0.999, 0, 0, 0, 0, 0.999, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 391 | transform: matrix3d(0.999, 0, 0, 0, 0, 0.999, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 392 | } 393 | 100% { 394 | -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 395 | transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 396 | } 397 | } 398 | @keyframes bounce { 399 | 0% { 400 | -webkit-transform: matrix3d(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 401 | transform: matrix3d(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 402 | } 403 | 4.7% { 404 | -webkit-transform: matrix3d(0.45, 0, 0, 0, 0, 0.45, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 405 | transform: matrix3d(0.45, 0, 0, 0, 0, 0.45, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 406 | } 407 | 9.41% { 408 | -webkit-transform: matrix3d(0.883, 0, 0, 0, 0, 0.883, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 409 | transform: matrix3d(0.883, 0, 0, 0, 0, 0.883, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 410 | } 411 | 14.11% { 412 | -webkit-transform: matrix3d(1.141, 0, 0, 0, 0, 1.141, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 413 | transform: matrix3d(1.141, 0, 0, 0, 0, 1.141, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 414 | } 415 | 18.72% { 416 | -webkit-transform: matrix3d(1.212, 0, 0, 0, 0, 1.212, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 417 | transform: matrix3d(1.212, 0, 0, 0, 0, 1.212, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 418 | } 419 | 24.32% { 420 | -webkit-transform: matrix3d(1.151, 0, 0, 0, 0, 1.151, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 421 | transform: matrix3d(1.151, 0, 0, 0, 0, 1.151, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 422 | } 423 | 29.93% { 424 | -webkit-transform: matrix3d(1.048, 0, 0, 0, 0, 1.048, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 425 | transform: matrix3d(1.048, 0, 0, 0, 0, 1.048, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 426 | } 427 | 35.54% { 428 | -webkit-transform: matrix3d(0.979, 0, 0, 0, 0, 0.979, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 429 | transform: matrix3d(0.979, 0, 0, 0, 0, 0.979, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 430 | } 431 | 41.04% { 432 | -webkit-transform: matrix3d(0.961, 0, 0, 0, 0, 0.961, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 433 | transform: matrix3d(0.961, 0, 0, 0, 0, 0.961, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 434 | } 435 | 52.15% { 436 | -webkit-transform: matrix3d(0.991, 0, 0, 0, 0, 0.991, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 437 | transform: matrix3d(0.991, 0, 0, 0, 0, 0.991, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 438 | } 439 | 63.26% { 440 | -webkit-transform: matrix3d(1.007, 0, 0, 0, 0, 1.007, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 441 | transform: matrix3d(1.007, 0, 0, 0, 0, 1.007, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 442 | } 443 | 85.49% { 444 | -webkit-transform: matrix3d(0.999, 0, 0, 0, 0, 0.999, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 445 | transform: matrix3d(0.999, 0, 0, 0, 0, 0.999, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 446 | } 447 | 100% { 448 | -webkit-transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 449 | transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); 450 | } 451 | } 452 | @-webkit-keyframes ball { 453 | from { 454 | -webkit-transform: translateY(0) scaleY(0.8); 455 | transform: translateY(0) scaleY(0.8); 456 | } 457 | to { 458 | -webkit-transform: translateY(-10px); 459 | transform: translateY(-10px); 460 | } 461 | } 462 | @keyframes ball { 463 | from { 464 | -webkit-transform: translateY(0) scaleY(0.8); 465 | transform: translateY(0) scaleY(0.8); 466 | } 467 | to { 468 | -webkit-transform: translateY(-10px); 469 | transform: translateY(-10px); 470 | } 471 | } 472 | -------------------------------------------------------------------------------- /ui/static/js/index.js: -------------------------------------------------------------------------------- 1 | var $messages = $('.messages-content'), 2 | d, h, m, 3 | i = 0; 4 | 5 | $(window).load(function() { 6 | $messages.mCustomScrollbar(); 7 | setTimeout(function() { 8 | //fakeMessage(); 9 | }, 100); 10 | }); 11 | 12 | function updateScrollbar() { 13 | $messages.mCustomScrollbar("update").mCustomScrollbar('scrollTo', 'bottom', { 14 | scrollInertia: 10, 15 | timeout: 0 16 | }); 17 | } 18 | 19 | function setDate(){ 20 | d = new Date() 21 | if (m != d.getMinutes()) { 22 | m = d.getMinutes(); 23 | $('
').appendTo($('.message:last')); 24 | } 25 | } 26 | 27 | function insertMessage() { 28 | msg = $('.message-input').val(); 29 | if ($.trim(msg) == '') { 30 | return false; 31 | } 32 | $(' ').appendTo($('.mCSB_container')).addClass('new'); 33 | setDate(); 34 | $('.message-input').val(null); 35 | updateScrollbar(); 36 | interact(msg); 37 | setTimeout(function() { 38 | //fakeMessage(); 39 | }, 1000 + (Math.random() * 20) * 100); 40 | } 41 | 42 | $('.message-submit').click(function() { 43 | insertMessage(); 44 | }); 45 | 46 | $(window).on('keydown', function(e) { 47 | if (e.which == 13) { 48 | insertMessage(); 49 | return false; 50 | } 51 | }) 52 | 53 | 54 | function interact(message){ 55 | // loading message 56 | $(' ').appendTo($('.mCSB_container')); 57 | // make a POST request [ajax call] 58 | $.post('/message', { 59 | msg: message, 60 | }).done(function(reply) { 61 | // Message Received 62 | // remove loading meassage 63 | $('.message.loading').remove(); 64 | // Add message to chatbox 65 | $(' ').appendTo($('.mCSB_container')).addClass('new'); 66 | setDate(); 67 | updateScrollbar(); 68 | 69 | }).fail(function() { 70 | alert('error calling function'); 71 | }); 72 | } 73 | -------------------------------------------------------------------------------- /ui/static/js/jquery.mCustomScrollbar.concat.min.js: -------------------------------------------------------------------------------- 1 | /* == jquery mousewheel plugin == Version: 3.1.13, License: MIT License (MIT) */ 2 | !function(a){"function"==typeof define&&define.amd?define(["jquery"],a):"object"==typeof exports?module.exports=a:a(jQuery)}(function(a){function b(b){var g=b||window.event,h=i.call(arguments,1),j=0,l=0,m=0,n=0,o=0,p=0;if(b=a.event.fix(g),b.type="mousewheel","detail"in g&&(m=-1*g.detail),"wheelDelta"in g&&(m=g.wheelDelta),"wheelDeltaY"in g&&(m=g.wheelDeltaY),"wheelDeltaX"in g&&(l=-1*g.wheelDeltaX),"axis"in g&&g.axis===g.HORIZONTAL_AXIS&&(l=-1*m,m=0),j=0===m?l:m,"deltaY"in g&&(m=-1*g.deltaY,j=m),"deltaX"in g&&(l=g.deltaX,0===m&&(j=-1*l)),0!==m||0!==l){if(1===g.deltaMode){var q=a.data(this,"mousewheel-line-height");j*=q,m*=q,l*=q}else if(2===g.deltaMode){var r=a.data(this,"mousewheel-page-height");j*=r,m*=r,l*=r}if(n=Math.max(Math.abs(m),Math.abs(l)),(!f||f>n)&&(f=n,d(g,n)&&(f/=40)),d(g,n)&&(j/=40,l/=40,m/=40),j=Math[j>=1?"floor":"ceil"](j/f),l=Math[l>=1?"floor":"ceil"](l/f),m=Math[m>=1?"floor":"ceil"](m/f),k.settings.normalizeOffset&&this.getBoundingClientRect){var s=this.getBoundingClientRect();o=b.clientX-s.left,p=b.clientY-s.top}return b.deltaX=l,b.deltaY=m,b.deltaFactor=f,b.offsetX=o,b.offsetY=p,b.deltaMode=0,h.unshift(b,j,l,m),e&&clearTimeout(e),e=setTimeout(c,200),(a.event.dispatch||a.event.handle).apply(this,h)}}function c(){f=null}function d(a,b){return k.settings.adjustOldDeltas&&"mousewheel"===a.type&&b%120===0}var e,f,g=["wheel","mousewheel","DOMMouseScroll","MozMousePixelScroll"],h="onwheel"in document||document.documentMode>=9?["wheel"]:["mousewheel","DomMouseScroll","MozMousePixelScroll"],i=Array.prototype.slice;if(a.event.fixHooks)for(var j=g.length;j;)a.event.fixHooks[g[--j]]=a.event.mouseHooks;var k=a.event.special.mousewheel={version:"3.1.12",setup:function(){if(this.addEventListener)for(var c=h.length;c;)this.addEventListener(h[--c],b,!1);else this.onmousewheel=b;a.data(this,"mousewheel-line-height",k.getLineHeight(this)),a.data(this,"mousewheel-page-height",k.getPageHeight(this))},teardown:function(){if(this.removeEventListener)for(var c=h.length;c;)this.removeEventListener(h[--c],b,!1);else this.onmousewheel=null;a.removeData(this,"mousewheel-line-height"),a.removeData(this,"mousewheel-page-height")},getLineHeight:function(b){var c=a(b),d=c["offsetParent"in a.fn?"offsetParent":"parent"]();return d.length||(d=a("body")),parseInt(d.css("fontSize"),10)||parseInt(c.css("fontSize"),10)||16},getPageHeight:function(b){return a(b).height()},settings:{adjustOldDeltas:!0,normalizeOffset:!0}};a.fn.extend({mousewheel:function(a){return a?this.bind("mousewheel",a):this.trigger("mousewheel")},unmousewheel:function(a){return this.unbind("mousewheel",a)}})});!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):"object"==typeof exports?module.exports=a:a(jQuery)}(function(a){function b(b){var g=b||window.event,h=i.call(arguments,1),j=0,l=0,m=0,n=0,o=0,p=0;if(b=a.event.fix(g),b.type="mousewheel","detail"in g&&(m=-1*g.detail),"wheelDelta"in g&&(m=g.wheelDelta),"wheelDeltaY"in g&&(m=g.wheelDeltaY),"wheelDeltaX"in g&&(l=-1*g.wheelDeltaX),"axis"in g&&g.axis===g.HORIZONTAL_AXIS&&(l=-1*m,m=0),j=0===m?l:m,"deltaY"in g&&(m=-1*g.deltaY,j=m),"deltaX"in g&&(l=g.deltaX,0===m&&(j=-1*l)),0!==m||0!==l){if(1===g.deltaMode){var q=a.data(this,"mousewheel-line-height");j*=q,m*=q,l*=q}else if(2===g.deltaMode){var r=a.data(this,"mousewheel-page-height");j*=r,m*=r,l*=r}if(n=Math.max(Math.abs(m),Math.abs(l)),(!f||f>n)&&(f=n,d(g,n)&&(f/=40)),d(g,n)&&(j/=40,l/=40,m/=40),j=Math[j>=1?"floor":"ceil"](j/f),l=Math[l>=1?"floor":"ceil"](l/f),m=Math[m>=1?"floor":"ceil"](m/f),k.settings.normalizeOffset&&this.getBoundingClientRect){var s=this.getBoundingClientRect();o=b.clientX-s.left,p=b.clientY-s.top}return b.deltaX=l,b.deltaY=m,b.deltaFactor=f,b.offsetX=o,b.offsetY=p,b.deltaMode=0,h.unshift(b,j,l,m),e&&clearTimeout(e),e=setTimeout(c,200),(a.event.dispatch||a.event.handle).apply(this,h)}}function c(){f=null}function d(a,b){return k.settings.adjustOldDeltas&&"mousewheel"===a.type&&b%120===0}var e,f,g=["wheel","mousewheel","DOMMouseScroll","MozMousePixelScroll"],h="onwheel"in document||document.documentMode>=9?["wheel"]:["mousewheel","DomMouseScroll","MozMousePixelScroll"],i=Array.prototype.slice;if(a.event.fixHooks)for(var j=g.length;j;)a.event.fixHooks[g[--j]]=a.event.mouseHooks;var k=a.event.special.mousewheel={version:"3.1.12",setup:function(){if(this.addEventListener)for(var c=h.length;c;)this.addEventListener(h[--c],b,!1);else this.onmousewheel=b;a.data(this,"mousewheel-line-height",k.getLineHeight(this)),a.data(this,"mousewheel-page-height",k.getPageHeight(this))},teardown:function(){if(this.removeEventListener)for(var c=h.length;c;)this.removeEventListener(h[--c],b,!1);else this.onmousewheel=null;a.removeData(this,"mousewheel-line-height"),a.removeData(this,"mousewheel-page-height")},getLineHeight:function(b){var c=a(b),d=c["offsetParent"in a.fn?"offsetParent":"parent"]();return d.length||(d=a("body")),parseInt(d.css("fontSize"),10)||parseInt(c.css("fontSize"),10)||16},getPageHeight:function(b){return a(b).height()},settings:{adjustOldDeltas:!0,normalizeOffset:!0}};a.fn.extend({mousewheel:function(a){return a?this.bind("mousewheel",a):this.trigger("mousewheel")},unmousewheel:function(a){return this.unbind("mousewheel",a)}})}); 3 | /* == malihu jquery custom scrollbar plugin == Version: 3.1.3, License: MIT License (MIT) */ 4 | !function(e){"undefined"!=typeof module&&module.exports?module.exports=e:e(jQuery,window,document)}(function(e){!function(t){var o="function"==typeof define&&define.amd,a="undefined"!=typeof module&&module.exports,n="https:"==document.location.protocol?"https:":"http:",i="cdnjs.cloudflare.com/ajax/libs/jquery-mousewheel/3.1.13/jquery.mousewheel.min.js";o||(a?require("jquery-mousewheel")(e):e.event.special.mousewheel||e("head").append(decodeURI("%3Cscript src="+n+"//"+i+"%3E%3C/script%3E"))),t()}(function(){var t,o="mCustomScrollbar",a="mCS",n=".mCustomScrollbar",i={setTop:0,setLeft:0,axis:"y",scrollbarPosition:"inside",scrollInertia:950,autoDraggerLength:!0,alwaysShowScrollbar:0,snapOffset:0,mouseWheel:{enable:!0,scrollAmount:"auto",axis:"y",deltaFactor:"auto",disableOver:["select","option","keygen","datalist","textarea"]},scrollButtons:{scrollType:"stepless",scrollAmount:"auto"},keyboard:{enable:!0,scrollType:"stepless",scrollAmount:"auto"},contentTouchScroll:25,documentTouchScroll:!0,advanced:{autoScrollOnFocus:"input,textarea,select,button,datalist,keygen,a[tabindex],area,object,[contenteditable='true']",updateOnContentResize:!0,updateOnImageLoad:"auto",autoUpdateTimeout:60},theme:"light",callbacks:{onTotalScrollOffset:0,onTotalScrollBackOffset:0,alwaysTriggerOffsets:!0}},r=0,l={},s=window.attachEvent&&!window.addEventListener?1:0,c=!1,d=["mCSB_dragger_onDrag","mCSB_scrollTools_onDrag","mCS_img_loaded","mCS_disabled","mCS_destroyed","mCS_no_scrollbar","mCS-autoHide","mCS-dir-rtl","mCS_no_scrollbar_y","mCS_no_scrollbar_x","mCS_y_hidden","mCS_x_hidden","mCSB_draggerContainer","mCSB_buttonUp","mCSB_buttonDown","mCSB_buttonLeft","mCSB_buttonRight"],u={init:function(t){var t=e.extend(!0,{},i,t),o=f.call(this);if(t.live){var s=t.liveSelector||this.selector||n,c=e(s);if("off"===t.live)return void m(s);l[s]=setTimeout(function(){c.mCustomScrollbar(t),"once"===t.live&&c.length&&m(s)},500)}else m(s);return t.setWidth=t.set_width?t.set_width:t.setWidth,t.setHeight=t.set_height?t.set_height:t.setHeight,t.axis=t.horizontalScroll?"x":p(t.axis),t.scrollInertia=t.scrollInertia>0&&t.scrollInertia<17?17:t.scrollInertia,"object"!=typeof t.mouseWheel&&1==t.mouseWheel&&(t.mouseWheel={enable:!0,scrollAmount:"auto",axis:"y",preventDefault:!1,deltaFactor:"auto",normalizeDelta:!1,invert:!1}),t.mouseWheel.scrollAmount=t.mouseWheelPixels?t.mouseWheelPixels:t.mouseWheel.scrollAmount,t.mouseWheel.normalizeDelta=t.advanced.normalizeMouseWheelDelta?t.advanced.normalizeMouseWheelDelta:t.mouseWheel.normalizeDelta,t.scrollButtons.scrollType=g(t.scrollButtons.scrollType),h(t),e(o).each(function(){var o=e(this);if(!o.data(a)){o.data(a,{idx:++r,opt:t,scrollRatio:{y:null,x:null},overflowed:null,contentReset:{y:null,x:null},bindEvents:!1,tweenRunning:!1,sequential:{},langDir:o.css("direction"),cbOffsets:null,trigger:null,poll:{size:{o:0,n:0},img:{o:0,n:0},change:{o:0,n:0}}});var n=o.data(a),i=n.opt,l=o.data("mcs-axis"),s=o.data("mcs-scrollbar-position"),c=o.data("mcs-theme");l&&(i.axis=l),s&&(i.scrollbarPosition=s),c&&(i.theme=c,h(i)),v.call(this),n&&i.callbacks.onCreate&&"function"==typeof i.callbacks.onCreate&&i.callbacks.onCreate.call(this),e("#mCSB_"+n.idx+"_container img:not(."+d[2]+")").addClass(d[2]),u.update.call(null,o)}})},update:function(t,o){var n=t||f.call(this);return e(n).each(function(){var t=e(this);if(t.data(a)){var n=t.data(a),i=n.opt,r=e("#mCSB_"+n.idx+"_container"),l=e("#mCSB_"+n.idx),s=[e("#mCSB_"+n.idx+"_dragger_vertical"),e("#mCSB_"+n.idx+"_dragger_horizontal")];if(!r.length)return;n.tweenRunning&&N(t),o&&n&&i.callbacks.onBeforeUpdate&&"function"==typeof i.callbacks.onBeforeUpdate&&i.callbacks.onBeforeUpdate.call(this),t.hasClass(d[3])&&t.removeClass(d[3]),t.hasClass(d[4])&&t.removeClass(d[4]),l.css("max-height","none"),l.height()!==t.height()&&l.css("max-height",t.height()),_.call(this),"y"===i.axis||i.advanced.autoExpandHorizontalScroll||r.css("width",x(r)),n.overflowed=y.call(this),M.call(this),i.autoDraggerLength&&S.call(this),b.call(this),T.call(this);var c=[Math.abs(r[0].offsetTop),Math.abs(r[0].offsetLeft)];"x"!==i.axis&&(n.overflowed[0]?s[0].height()>s[0].parent().height()?B.call(this):(V(t,c[0].toString(),{dir:"y",dur:0,overwrite:"none"}),n.contentReset.y=null):(B.call(this),"y"===i.axis?k.call(this):"yx"===i.axis&&n.overflowed[1]&&V(t,c[1].toString(),{dir:"x",dur:0,overwrite:"none"}))),"y"!==i.axis&&(n.overflowed[1]?s[1].width()>s[1].parent().width()?B.call(this):(V(t,c[1].toString(),{dir:"x",dur:0,overwrite:"none"}),n.contentReset.x=null):(B.call(this),"x"===i.axis?k.call(this):"yx"===i.axis&&n.overflowed[0]&&V(t,c[0].toString(),{dir:"y",dur:0,overwrite:"none"}))),o&&n&&(2===o&&i.callbacks.onImageLoad&&"function"==typeof i.callbacks.onImageLoad?i.callbacks.onImageLoad.call(this):3===o&&i.callbacks.onSelectorChange&&"function"==typeof i.callbacks.onSelectorChange?i.callbacks.onSelectorChange.call(this):i.callbacks.onUpdate&&"function"==typeof i.callbacks.onUpdate&&i.callbacks.onUpdate.call(this)),X.call(this)}})},scrollTo:function(t,o){if("undefined"!=typeof t&&null!=t){var n=f.call(this);return e(n).each(function(){var n=e(this);if(n.data(a)){var i=n.data(a),r=i.opt,l={trigger:"external",scrollInertia:r.scrollInertia,scrollEasing:"mcsEaseInOut",moveDragger:!1,timeout:60,callbacks:!0,onStart:!0,onUpdate:!0,onComplete:!0},s=e.extend(!0,{},l,o),c=q.call(this,t),d=s.scrollInertia>0&&s.scrollInertia<17?17:s.scrollInertia;c[0]=Y.call(this,c[0],"y"),c[1]=Y.call(this,c[1],"x"),s.moveDragger&&(c[0]*=i.scrollRatio.y,c[1]*=i.scrollRatio.x),s.dur=oe()?0:d,setTimeout(function(){null!==c[0]&&"undefined"!=typeof c[0]&&"x"!==r.axis&&i.overflowed[0]&&(s.dir="y",s.overwrite="all",V(n,c[0].toString(),s)),null!==c[1]&&"undefined"!=typeof c[1]&&"y"!==r.axis&&i.overflowed[1]&&(s.dir="x",s.overwrite="none",V(n,c[1].toString(),s))},s.timeout)}})}},stop:function(){var t=f.call(this);return e(t).each(function(){var t=e(this);t.data(a)&&N(t)})},disable:function(t){var o=f.call(this);return e(o).each(function(){var o=e(this);if(o.data(a)){{o.data(a)}X.call(this,"remove"),k.call(this),t&&B.call(this),M.call(this,!0),o.addClass(d[3])}})},destroy:function(){var t=f.call(this);return e(t).each(function(){var n=e(this);if(n.data(a)){var i=n.data(a),r=i.opt,l=e("#mCSB_"+i.idx),s=e("#mCSB_"+i.idx+"_container"),c=e(".mCSB_"+i.idx+"_scrollbar");r.live&&m(r.liveSelector||e(t).selector),X.call(this,"remove"),k.call(this),B.call(this),n.removeData(a),K(this,"mcs"),c.remove(),s.find("img."+d[2]).removeClass(d[2]),l.replaceWith(s.contents()),n.removeClass(o+" _"+a+"_"+i.idx+" "+d[6]+" "+d[7]+" "+d[5]+" "+d[3]).addClass(d[4])}})}},f=function(){return"object"!=typeof e(this)||e(this).length<1?n:this},h=function(t){var o=["rounded","rounded-dark","rounded-dots","rounded-dots-dark"],a=["rounded-dots","rounded-dots-dark","3d","3d-dark","3d-thick","3d-thick-dark","inset","inset-dark","inset-2","inset-2-dark","inset-3","inset-3-dark"],n=["minimal","minimal-dark"],i=["minimal","minimal-dark"],r=["minimal","minimal-dark"];t.autoDraggerLength=e.inArray(t.theme,o)>-1?!1:t.autoDraggerLength,t.autoExpandScrollbar=e.inArray(t.theme,a)>-1?!1:t.autoExpandScrollbar,t.scrollButtons.enable=e.inArray(t.theme,n)>-1?!1:t.scrollButtons.enable,t.autoHideScrollbar=e.inArray(t.theme,i)>-1?!0:t.autoHideScrollbar,t.scrollbarPosition=e.inArray(t.theme,r)>-1?"outside":t.scrollbarPosition},m=function(e){l[e]&&(clearTimeout(l[e]),K(l,e))},p=function(e){return"yx"===e||"xy"===e||"auto"===e?"yx":"x"===e||"horizontal"===e?"x":"y"},g=function(e){return"stepped"===e||"pixels"===e||"step"===e||"click"===e?"stepped":"stepless"},v=function(){var t=e(this),n=t.data(a),i=n.opt,r=i.autoExpandScrollbar?" "+d[1]+"_expand":"",l=[" "," "],s="yx"===i.axis?"mCSB_vertical_horizontal":"x"===i.axis?"mCSB_horizontal":"mCSB_vertical",c="yx"===i.axis?l[0]+l[1]:"x"===i.axis?l[1]:l[0],u="yx"===i.axis?"":"",f=i.autoHideScrollbar?" "+d[6]:"",h="x"!==i.axis&&"rtl"===n.langDir?" "+d[7]:"";i.setWidth&&t.css("width",i.setWidth),i.setHeight&&t.css("height",i.setHeight),i.setLeft="y"!==i.axis&&"rtl"===n.langDir?"989999px":i.setLeft,t.addClass(o+" _"+a+"_"+n.idx+f+h).wrapInner("